2008-08-08 8 views
32

フォーム内のオブジェクトのイベントをサブスクライブすると、コールバックメソッドの制御がイベントソースに引き渡されます。そのイベントソースが別のスレッドでイベントをトリガーするかどうかはわかりません。勝利フォームのスレッドセーフなイベントコールバックを作成するにはどうすればよいですか?

問題は、コールバックが呼び出されたときに、フォーム上の更新コントロールを作成することができないということです。フォームのスレッドとは異なるスレッドでイベントコールバックが呼び出された場合、走るここで

答えて

31

Simonのコードを単純化するために、組み込みの汎用Actionデリゲートを使用することができます。それはあなたが本当に必要としないデリゲート型の束であなたのコードを補うことを節約します。また、.NET 3.5では、一時的な配列を定義する必要がないように、invokesメソッドにparamsパラメータを追加しました。

私は、このシナリオで匿名メソッドをたくさん使う
void SomethingHappened(object sender, EventArgs ea) 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea); 
     return; 
    } 

    textBox1.Text = "Something happened"; 
} 
15

は顕著なポイントです:

  1. あなたは彼らが(フォームのスレッド)で作成されたものとは別のスレッドからUIコントロールの呼び出しを行うことはできません。
  2. デリゲートの呼び出し(イベントフック)は、イベントを発生させているオブジェクトと同じスレッドでトリガされます。

あなたには、いくつかの仕事をしている別の「エンジン」のスレッドを持っているし、(そのようなプログレスバーまたは任意のような)UIに反映させることができる状態の変化を監視いくつかのUIを持っているのであれば、あなたは問題を抱えています。エンジンの火は、フォームによって引かれたオブジェクト変更イベントです。しかし、コールバックは、エンジンに登録されたフォームがエンジンのスレッドで呼び出され、フォームのスレッドでは呼び出されないことを委任します。したがって、そのコールバックからコントロールを更新することはできません。ドー!

BeginInvokeが救助に来ます。ただ、すべてのコールバックメソッドでは、この単純な符号化モデルを使用して、あなたは物事が大丈夫であることを行っていることを確認することができます:

private delegate void EventArgsDelegate(object sender, EventArgs ea); 

void SomethingHappened(object sender, EventArgs ea) 
{ 
    // 
    // Make sure this callback is on the correct thread 
    // 
    if (this.InvokeRequired) 
    { 
     this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea }); 
     return; 
    } 

    // 
    // Do something with the event such as update a control 
    // 
    textBox1.Text = "Something happened"; 
} 

それは実際にはかなり簡単です。

  1. 使用は、このコールバックが正しいスレッド上で起きたかどうかを確認するためにをInvokeRequired。
  2. そうでない場合は、同じパラメータで正しいスレッドでコールバックを再呼び出しします。 Invoke(ブロッキング)またはBeginInvoke(非ブロッキング)メソッドを使用して、メソッドを再呼び出しできます。
  3. 次回の関数呼び出し時に、InvokeRequiredは、正しいスレッドを使用していて誰もが満足しているため、falseを返します。

これは、この問題に対処し、フォームをマルチスレッドのイベントコールバックから安全にする非常に簡単な方法です。

+1

一般的に私はBeginInvokeを呼び出す方が好きですが、警告があります:あまりにも多くのイベントをキューに入れないでください。 BeginInvokeが起きたときに1に設定されたupdateRequired変数を使用し、BeginInvokeが0(Interlocked.Exchangeを使用している)の場合にのみBeginInvokeを実行します。表示ハンドラにはwhileループがあり、updateRequiredをクリアし、ゼロでない場合は更新とループを行います。場合によっては、更新頻度をさらに制限するためのタイマーが追加されています(実際の作業を行う代わりに、コードのすべての時間を進捗状況の更新に費やすことを避けるため)が、それはより複雑です。 – supercat

+0

@Supercat ...イベントスロットリングは、多くのアプリケーションにとって重要なトピックですが、UIレイヤの一部でなければならないものではありません。適切な間隔でイベントを受信し、キューに入れ、結合し、再送するために、別々のイベント・プロキシ・バスを作成する必要があります。イベントバスの加入者は、イベントスロットリングが発生していることを知るべきではありません。 –

+0

同期を処理する別の "イベントバス"が役に立つ場所を見ることができますが、多くの場合、クラスが単にMinimumUpdateIntervalプロパティを公開している場合は、進行状況インジケータクラスのようなものをエンドユーザにとって最も簡単に見えるでしょう。 – supercat

0

多くの単純なケースでは、MethodInvokerデリゲートを使用して、独自のデリゲートタイプを作成する必要がなくなります。

9

void SomethingHappened(object sender, EventArgs ea) 
{ 
    MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
    InvokeRequired ? Invoke(del) : del(); 
} 
2

私は少し遅れて、このトピックにだけど、あなたはEvent-Based Asynchronous Patternを見てみたいことがあります。適切に実装されると、イベントが常にUIスレッドから発生することが保証されます。

ここでは、1回の同時呼び出しのみを許可する簡単な例を示します。複数の呼び出し/イベントをサポートするには、もう少し配管が必要です。 lazy programmerとして

using System; 
using System.ComponentModel; 
using System.Threading; 
using System.Windows.Forms; 

namespace WindowsFormsApplication1 
{ 
    public class MainForm : Form 
    { 
     private TypeWithAsync _type; 

     [STAThread()] 
     public static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.Run(new MainForm()); 
     } 

     public MainForm() 
     { 
      _type = new TypeWithAsync(); 
      _type.DoSomethingCompleted += DoSomethingCompleted; 

      var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill }; 

      var btn = new Button() { Text = "Synchronous" }; 
      btn.Click += SyncClick; 
      panel.Controls.Add(btn); 

      btn = new Button { Text = "Asynchronous" }; 
      btn.Click += AsyncClick; 
      panel.Controls.Add(btn); 

      Controls.Add(panel); 
     } 

     private void SyncClick(object sender, EventArgs e) 
     { 
      int value = _type.DoSomething(); 
      MessageBox.Show(string.Format("DoSomething() returned {0}.", value)); 
     } 

     private void AsyncClick(object sender, EventArgs e) 
     { 
      _type.DoSomethingAsync(); 
     } 

     private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e) 
     { 
      MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value)); 
     } 
    } 

    class TypeWithAsync 
    { 
     private AsyncOperation _operation; 

     // synchronous version of method 
     public int DoSomething() 
     { 
      Thread.Sleep(5000); 
      return 27; 
     } 

     // async version of method 
     public void DoSomethingAsync() 
     { 
      if (_operation != null) 
      { 
       throw new InvalidOperationException("An async operation is already running."); 
      } 

      _operation = AsyncOperationManager.CreateOperation(null); 
      ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore); 
     } 

     // wrapper used by async method to call sync version of method, matches WaitCallback so it 
     // can be queued by the thread pool 
     private void DoSomethingAsyncCore(object state) 
     { 
      int returnValue = DoSomething(); 
      var e = new DoSomethingCompletedEventArgs(returnValue); 
      _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e); 
     } 

     // wrapper used so async method can raise the event; matches SendOrPostCallback 
     private void RaiseDoSomethingCompleted(object args) 
     { 
      OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args); 
     } 

     private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e) 
     { 
      var handler = DoSomethingCompleted; 

      if (handler != null) { handler(this, e); } 
     } 

     public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted; 
    } 

    public class DoSomethingCompletedEventArgs : EventArgs 
    { 
     private int _value; 

     public DoSomethingCompletedEventArgs(int value) 
      : base() 
     { 
      _value = value; 
     } 

     public int Value 
     { 
      get { return _value; } 
     } 
    } 
} 
+1

「UIスレッドからイベントが常に発生することが保証されている」と言うのは、ちょっと誤解していると思います。イベントハンドラがタスクが作成された同じSynchronizationContext /スレッド上で確実に実行されることを確実にすると言うのはより正確でしょうか? (これはUIスレッド/ SynchronizationContextではないかもしれません) – jspaey

1

、私はこれを行うための非常に怠惰な方法があります。

私がしているのはこれだけです。

private void DoInvoke(MethodInvoker del) { 
    if (InvokeRequired) { 
     Invoke(del); 
    } else { 
     del(); 
    } 
} 
//example of how to call it 
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) { 
    DoInvoke(delegate { lbl.Text = val; }); 
} 

DoInvokeを関数内でインライン化したり、別の関数内で非表示にしたりすることができます。

関数を直接DoInvokeメソッドに渡すことができます。

+0

私はすべてのプログラミングに夢中です:) .NET 3.5以上を使用しているなら、 'Action'または' Action 'とlambda式: 'Doinvoke(()=> textLabel.Text ="何か ")' –

関連する問題