25

タイマーで遊んでいます。コンテキスト: 2 つのラベルを持つ winforms。

System.Timers.TimerFormsタイマーを使用していないので、どのように機能するかを確認したいと思います。フォームと 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(); }

    }
  }
}
4

5 に答える 5

44

あなたのコードは単なるテストだと思うので、タイマーで何をするかについては説明しません。ここでの問題は、タイマー コールバック内のユーザー インターフェイス コントロールで何かを行う方法です。

のメソッドとプロパティのほとんどはControl、UI スレッドからのみアクセスできます (実際には、それらを作成したスレッドからのみアクセスできますが、これは別の話です)。これは、各スレッドに独自のメッセージ ループ (スレッドごとにメッセージGetMessage()をフィルター処理する) が必要でControlあり、スレッドからメインスレッドにメッセージをディスパッチする必要があるためです。Control.NET では、この目的のために every がいくつかのメソッドを継承するため、簡単です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";
    }));
}

InvalidateBeginInvokeEndInvoke、メソッドを常に呼び出してプロパティInvokeを読み取る ことができるリファレンスと同様に、任意のスレッドから呼び出すことができるメソッドのリストについては、MSDN を確認してください。一般に、これは一般的な使用パターンです (が から派生したオブジェクトであるとInvokeRequired仮定します)。thisControl

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)。

作業中のスレッドがあるとします。メイン スレッドを呼び出すにはどうすればよいでしょうか。最も簡単な方法は、この基本的な構造を使用してトリックを行うことです。タスクを単純化しすぎていますが、手順は次のとおりです。

  • 呼び出す関数の (スレッドセーフな) キューを作成します (いくつかの例は SO にあります)。
  • カスタム メッセージをウィンドウ プロシージャに投稿します。このキューを優先度キューにすると、これらの呼び出しの優先度を決定することもできます (たとえば、作業スレッドからの進行状況通知は、アラーム通知よりも優先度が低くなる場合があります)。
  • ウィンドウ プロシージャ (switch/case ステートメント内) でそのメッセージを理解すると、関数をピークしてキューから呼び出し、呼び出すことができます。

WPF と WinForms はどちらも、このメソッドを使用して、スレッドから UI スレッドにメッセージを配信 (ディスパッチ) します。複数のスレッドとユーザー インターフェイスの詳細については、MSDN のこの記事を参照してください。WinForms はこれらの詳細の多くを隠しているため、それらを処理する必要はありませんが、内部でどのように機能するかを理解するために見てみるとよいでしょう。

于 2012-04-16T08:15:57.193 に答える
16

個人的には、UI 以外のスレッドで動作するアプリケーションで作業するときは、通常、次の小さなスニペットを記述します。

private void InvokeUI(Action a)
{
    this.BeginInvoke(new MethodInvoker(a));
}

別のスレッドで非同期呼び出しを行うときは、次を使用して常にコールバックできます。

InvokeUI(() => { 
   Label1.Text = "Super Cool";
});

シンプルでクリーン。

于 2016-03-19T22:20:34.127 に答える
2

尋ねられたように、これがクロススレッド呼び出しをチェックし、変数の更新を同期し、タイマーを停止および開始せず、経過時間をカウントするためにタイマーを使用しないという私の答えです。

固定BeginInvoke呼び出しを編集します。ジェネリックを使用してクロススレッド呼び出しを実行しましたAction。これにより、senderとeventargsを渡すことができます。これらが使用されていない場合(ここにあるように)、使用する方が効率的ですが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;
            }
        }
    }
}
于 2012-04-16T08:37:53.703 に答える
1

まず、Windowsフォーム(およびほとんどのフレームワーク)では、コントロールにアクセスできるのはUIスレッドのみです(「スレッドセーフ」として文書化されている場合を除く)。

だからthis.lblElapsedTime.Text = ...あなたのコールバックでは明らかに間違っています。Control.BeginInvokeを見てください。

次に、時間の計算にはSystem.DateTimeSystem.TimeSpanを使用する必要があります。

未テスト:

DateTime startTime = DateTime.Now;

void myTimer_Elapsed(...) {
  TimeSpan elapsed = DateTime.Now - startTime;
  this.lblElapsedTime.BeginInvoke(delegate() {
    this.lblElapsedTime.Text = elapsed.ToString();
  });
}
于 2012-04-16T08:17:43.087 に答える
0

以下を使用して終了しました。それは与えられた提案の組み合わせです:

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();}
      }
  }
}
于 2012-04-16T13:12:48.103 に答える