タイマーで遊んでいます。コンテキスト:2つのラベルを持つwinforms。
System.Timers.Timer
は機能するため、Formsタイマーは使用していません。フォームとmyTimerが異なるスレッドで実行されることを理解しています。 lblValue
の経過時間を次の形式で表す簡単な方法はありますか?
私はここで [〜#〜] msdn [〜#〜] を見ましたが、もっと簡単な方法があります!
Winformsのコードは次のとおりです。
using System.Timers;
namespace Ariport_Parking
{
public partial class AirportParking : Form
{
//instance variables of the form
System.Timers.Timer myTimer;
int ElapsedCounter = 0;
int MaxTime = 5000;
int elapsedTime = 0;
static int tickLength = 100;
public AirportParking()
{
InitializeComponent();
keepingTime();
lblValue.Text = "hello";
}
//method for keeping time
public void keepingTime() {
myTimer = new System.Timers.Timer(tickLength);
myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){
myTimer.Stop();
ElapsedCounter += 1;
elapsedTime += tickLength;
if (elapsedTime < MaxTime)
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
myTimer.Start();
}
else
{ myTimer.Start(); }
}
}
}
コードは単なるテストであるため、タイマーで何をするかについては説明しません。ここでの問題は、タイマーコールバック内のユーザーインターフェイスコントロールで何かを行う方法です。
Control
のほとんどのメソッドとプロパティは、UIスレッドからのみアクセスできます(実際には、それらを作成したスレッドからのみアクセスできますが、これは別の話です)。これは、各スレッドが独自のメッセージループ(GetMessage()
がスレッドごとにメッセージをフィルタリングする)を持たなければならず、Control
を使用して何かを行うには、スレッドからmainスレッド。 .NETでは、すべてのControl
がこの目的のためにいくつかのメソッドを継承するため、簡単です:Invoke/BeginInvoke/EndInvoke
。実行中のスレッドがそれらのメソッドを呼び出す必要があるかどうかを知るには、プロパティInvokeRequired
があります。これを使用してコードを変更するだけで機能します:
if (elapsedTime < MaxTime)
{
this.BeginInvoke(new MethodInvoker(delegate
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
}));
}
どのスレッドからも呼び出すことができるメソッドのリストについては、MSDNを確認してください。参照として常にInvalidate
、BeginInvoke
、EndInvoke
、Invoke
メソッドを呼び出すことができますInvokeRequired
プロパティを読み取ります。一般的に、これは一般的な使用パターンです(this
がControl
から派生したオブジェクトであると仮定):
void DoStuff() {
// Has been called from a "wrong" thread?
if (InvokeRequired) {
// Dispatch to correct thread, use BeginInvoke if you don't need
// caller thread until operation completes
Invoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}
UIスレッドがメソッドの実行を完了するまで、現在のスレッドはブロックされることに注意してください。これは、スレッドのタイミングが重要な場合に問題になる可能性があります(UIスレッドがビジーであるか、少しハングする可能性があることを忘れないでください)。メソッドの戻り値が必要ない場合は、単にInvoke
をBeginInvoke
に置き換えることができます。WinFormsの場合、EndInvoke
への後続の呼び出しさえ必要ありません。
void DoStuff() {
if (InvokeRequired) {
BeginInvoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}
戻り値が必要な場合は、通常のIAsyncResult
インターフェイスを処理する必要があります。
GUI Windowsアプリケーションは、メッセージループを持つウィンドウプロシージャに基づいています。プレーンCでアプリケーションを作成する場合、次のようなものがあります。
MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
これらの数行のコードで、アプリケーションはメッセージを待ってからウィンドウプロシージャにメッセージを配信します。ウィンドウプロシージャは、メッセージを確認する大きなswitch/caseステートメントです(WM_
)知っていて、何らかの方法で処理します(WM_Paint
、あなたはWM_QUIT
等々)。
作業スレッドがあると想像してください。どのようにメインスレッドをcallできますか?最も簡単な方法は、この基本構造を使用してトリックを行うことです。タスクを単純化しますが、次の手順を実行します。
WPFとWinFormsはどちらもこのメソッドを使用して、スレッドからUIスレッドにメッセージを配信(ディスパッチ)します。複数のスレッドとユーザーインターフェイスの詳細については MSDNのこの記事 を参照してください。WinFormsはこれらの詳細の多くを隠しており、それらの世話をする必要はありませんが、ボンネットの下で動作します。
個人的には、UIのスレッド外で動作するアプリケーションで作業するとき、私は通常この小さな断片を書きます。
private void InvokeUI(Action a)
{
this.BeginInvoke(new MethodInvoker(a));
}
異なるスレッドで非同期呼び出しを行うと、常にコールバックを使用できます。
InvokeUI(() => {
Label1.Text = "Super Cool";
});
シンプルできれい。
質問通り、クロススレッドコールをチェックし、変数の更新を同期し、タイマーを停止および開始せず、経過時間をカウントするためにタイマーを使用しない私の答えがあります。
[〜#〜] edit [〜#〜] fixed BeginInvoke
呼び出し。汎用のAction
を使用してクロススレッド呼び出しを実行しました。これにより、送信者とイベント引数を渡すことができます。これらが未使用の場合(ここにあるように)MethodInvoker
を使用する方が効率的ですが、処理をパラメーターのないメソッドに移動する必要があると思います。
public partial class AirportParking : Form
{
private Timer myTimer = new Timer(100);
private int elapsedCounter = 0;
private readonly DateTime startTime = DateTime.Now;
private const string EvenText = "hello";
private const string OddText = "hello world";
public AirportParking()
{
lblValue.Text = EvenText;
myTimer.Elapsed += MyTimerElapsed;
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
private void MyTimerElapsed(object sender,EventArgs myEventArgs)
{
If (lblValue.InvokeRequired)
{
var self = new Action<object, EventArgs>(MyTimerElapsed);
this.BeginInvoke(self, new [] {sender, myEventArgs});
return;
}
lock (this)
{
lblElapsedTime.Text = DateTime.Now.SubTract(startTime).ToString();
elapesedCounter++;
if(elapsedCounter % 2 == 0)
{
lblValue.Text = EvenText;
}
else
{
lblValue.Text = OddText;
}
}
}
}
まず、Windowsフォーム(およびほとんどのフレームワーク)では、UIスレッドからのみコントロールにアクセスできます(「スレッドセーフ」と記載されている場合を除く)。
そう this.lblElapsedTime.Text = ...
コールバックの間違いです。 Control.BeginInvoke をご覧ください。
次に、時間の計算に System.DateTime および System.TimeSpan を使用する必要があります。
未テスト:
DateTime startTime = DateTime.Now;
void myTimer_Elapsed(...) {
TimeSpan elapsed = DateTime.Now - startTime;
this.lblElapsedTime.BeginInvoke(delegate() {
this.lblElapsedTime.Text = elapsed.ToString();
});
}
次を使用して終了しました。それは与えられた提案の組み合わせです:
using System.Timers;
namespace Ariport_Parking
{
public partial class AirportParking : Form
{
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//instance variables of the form
System.Timers.Timer myTimer;
private const string EvenText = "hello";
private const string OddText = "hello world";
static int tickLength = 100;
static int elapsedCounter;
private int MaxTime = 5000;
private TimeSpan elapsedTime;
private readonly DateTime startTime = DateTime.Now;
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
public AirportParking()
{
InitializeComponent();
lblValue.Text = EvenText;
keepingTime();
}
//method for keeping time
public void keepingTime() {
using (System.Timers.Timer myTimer = new System.Timers.Timer(tickLength))
{
myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
}
private void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){
elapsedCounter++;
elapsedTime = DateTime.Now.Subtract(startTime);
if (elapsedTime.TotalMilliseconds < MaxTime)
{
this.BeginInvoke(new MethodInvoker(delegate
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (elapsedCounter % 2 == 0)
this.lblValue.Text = EvenText;
else
this.lblValue.Text = OddText;
}));
}
else {myTimer.Stop();}
}
}
}