web-dev-qa-db-ja.com

Dispatcher Invoke(...)vs BeginInvoke(...)の混乱

Count()メソッドのDispatcherで「BeginInvoke」を使用して、2つ(またはそれ以上)の同時実行カウンターテキストボックスでこのテストカウンターアプリケーションを動作させることができない理由を混乱させています。

BeginInvokeをInvokeに置き換えることで問題を解決できます。しかし、これは私の混乱を解決しません。

これが私が話しているサンプルコードです。

public class CounterTextBox : TextBox
{
    private int _number;

    public void Start()
    {
        (new Action(Count)).BeginInvoke(null, null);
    }

    private void Count()
    {
        while (true)
        {
            if (_number++ > 10000) _number = 0;
            this.Dispatcher.BeginInvoke(new Action(UpdateText), System.Windows.Threading.DispatcherPriority.Background, null);    
        }
    }

    private void UpdateText()
    {
        this.Text = "" + _number;
    }
}
28
Jerev

Dispatcher.BeginInvokeを使用する場合、指定されたアクションをschedules後の時点でUIスレッドで実行することを意味し、その後戻ります現在のスレッドが実行を継続できるようにする制御。 Invokeは、スケジュールされたアクションが終了するまで呼び出し元をブロックします。

BeginInvokeを使用すると、BeginInvokeはすぐに戻るため、ループはsuperで高速に実行されます。これは、アクションのlotおよびlotsを追加していることを意味しますメッセージキュー。それらを実際に処理できるよりも速くmuch追加しています。これは、メッセージをスケジュールしてから実際に実行されるまでに時間がかかることを意味します。

実行している実際のアクションは、_numberフィールドを使用します。ただし、_numberは、アクションがキューにある間、他のスレッドによって非常に迅速におよびによって変更されています。これは、アクションをスケジュールしたときに_numberの値を表示するのではなく、非常にタイトなループで継続した後の状態を表示することを意味します。

代わりにDispatcher.Invokeを使用すると、ループが「先に進む」ことを防ぎ、複数のスケジュールされたイベントを持つことを防ぎます。これにより、書き込む値は常に「現在の」値になります。さらに、ループの各反復がメッセージの実行を待機するように強制することにより、ループの「タイト」性が大幅に低下するため、一般的に迅速に実行できません。

BeginInvokeを使用する場合、最初に本当に必要なことは、ループを遅くすることです。 1秒ごと、または10ミリ秒など、テキストを更新する場合は、Thread.Sleepを使用して適切な時間待機できます。

次に、Dispatcherに渡す前に、_numberのコピーを取得して、実行時ではなく、スケジュールした時点で値を表示する必要があります。

while (true)
{
    if (_number++ > 10000)
        _number = 0;
    int copy = _number;
    this.Dispatcher.BeginInvoke(new Action(() => UpdateText(copy))
        , System.Windows.Threading.DispatcherPriority.Background, null);
    Thread.Sleep(200);
}

private void UpdateText(int number)
{
    this.Text = number.ToString();
}
71
Servy