2008-08-22 10 views
73

.NETイベントモデルは、あるスレッドでイベントを発生させ、別のスレッドでリッスンすることが多いようです。バックグラウンドスレッドからUIスレッドにイベントをマーシャリングする最もクリーンな方法は何ですか?クロススレッドイベントを呼び出す最もクリーンな方法

コミュニティの提案に基づいて、私はこれを使用しました:私は冗長デリゲートの宣言を避ける

// earlier in the code 
mCoolObject.CoolEvent+= 
      new CoolObjectEventHandler(mCoolObject_CoolEvent); 
// then 
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    if (InvokeRequired) 
    { 
     CoolObjectEventHandler cb = 
      new CoolObjectEventHandler(
       mCoolObject_CoolEvent); 
     Invoke(cb, new object[] { sender, args }); 
     return; 
    } 
    // do the dirty work of my method here 
} 
+0

既存の管理対象のコントロールに管理対象ハンドルがまだない場合、InvokeRequiredがfalseを返す可能性があることに注意してください。コントロールが完全に作成される前に発生するイベントには注意を払う必要があります。 – GregC

答えて

24

観測のカップル:

  • は、明示的にシンプルなデリゲートを作成しないでくださいあなたが2.0より前のものでなければ、次のコードを使用することができます。
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
       sender, 
       args); 
  • argsパラメータが "params"型であるため、オブジェクト配列を作成して移入する必要はありません。リストに渡すだけです。後者は非同期的に呼び出されるコードになりますよう

  • 私はおそらくBeginInvokeInvokeを好むと思われるかもしれないか、あなたは後にしているが、EndInvokeへの呼び出しなしに伝播することは困難、後続の例外を処理するだろう何もないかもしれません。あなたのアプリはTargetInvocationExceptionに変わります。

11

を。

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args); 
     return; 
    } 
    // do the dirty work of my method here 
} 

非イベントの場合、あなたはSystem.Windows.Forms.MethodInvokerデリゲートまたはSystem.Actionを使用することができます。

EDIT:さらに、すべてのイベントに対応するEventHandlerデリゲートがあるため、再デリートする必要はありません。その呼び出しが必要とされていると私はいつもそれが常にがどのように高価な不思議に思っていました

+1

私の場合は、このように動作します: 'Invoke(新しいアクション(mCoolObject_CoolEvent)、sender、args); ' –

+0

@ToniAlmeidaはい、それは私のコードのタイプミスです。それを指摘してくれてありがとう。 –

0

...

private void OnCoolEvent(CoolObjectEventArgs e) 
{ 
    BeginInvoke((o,e) => /*do work here*/,this, e); 
} 
+1

GUIスレッド内でBeginInvokeを実行すると、次にUIスレッドがWindowsメッセージを処理するまで、問題のアクションが遅延されます。これは、実際には、場合によっては便利なことです。 – supercat

0

あなたは、入力としてSynchronizationContextを受け入れ、イベントを呼び出すためにそれを使用して、一般的な構成要素のいくつかの並べ替えを開発しようとすることができます。

2

興味深い副作用として、WPFのバインディングは自動的にマーシャリングを処理するため、特別な操作をせずに背景スレッドで変更されたオブジェクトプロパティにUIをバインドできます。これは私にとって素晴らしいタイムセーバーであることが証明されています。 XAMLで

<TextBox Text="{Binding Path=Name}"/> 
+0

これは動作しません。一度あなたは例外を..非UIスレッド上の小道具を設定する..名前= "gbc"バン!失敗...フリーチーズメイトはありません –

+0

無料です(実行時間がかかる)が、wpfバインディングマシンは自動的にクロススレッドマーシャリングを処理するように見えます。私たちはバックグラウンドスレッドで受け取ったネットワークデータによって更新される小道具でこれを多く使用します。ここに説明があります:http://blog.lab49.com/archives/1166 – gbc

+0

@gbc Aaaaand説明がなくなった404. –

40

私はsome code for thisオンラインを持っています。それは他の提案よりもずっといいです。間違いなくそれをチェックしてください。

使用例:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args) 
{ 
    // You could use "() =>" in place of "delegate"; it's a style choice. 
    this.Invoke(delegate 
    { 
     // Do the dirty work of my method here. 
    }); 
} 
+2

+1拡張メソッドの非LINQ有用性を実証するため。 – galaktor

+0

優れたもの。 – Joe

+0

また、ネームスペースをあなたのエクステンションの 'System.Windows.Forms'に変更することもできます。こうすることで、必要なときに_カスタムの名前空間_を追加することを避けることができます。 –

3

私はクリーンな方法は、間違い AOPルートを行くことであると思います。いくつかの面を作って、必要な属性を追加すると、スレッドアフィニティを再度確認する必要はありません。

+0

私はあなたの提案を理解していません。 C#はネイティブなアスペクト指向言語ではありません。舞台裏でマーシャリングを実装する側面を実装するためのパターンやライブラリがありますか? – Eric

+0

私はPostSharpを使用するので、属性クラスでスレッディング動作を定義し、UIスレッドで呼び出される必要があるすべてのメソッドの前に[WpfThread]属性を使用します。 –

+0

それは魅力的です...私はそれを試してみる必要があります。 – Eric

2

私は自分の目的のために、次の「ユニバーサル」クロススレッド呼び出しクラスを作ったが、私はそれを共有する価値があると思う:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Forms; 

namespace CrossThreadCalls 
{ 
    public static class clsCrossThreadCalls 
    { 
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value); 
    public static void SetAnyProperty(Control c, string Property, object Value) 
    { 
     if (c.GetType().GetProperty(Property) != null) 
     { 
     //The given property exists 
     if (c.InvokeRequired) 
     { 
      SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty); 
      c.BeginInvoke(d, c, Property, Value); 
     } 
     else 
     { 
      c.GetType().GetProperty(Property).SetValue(c, Value, null); 
     } 
     } 
    } 

    private delegate void SetTextPropertyCallBack(Control c, string Value); 
    public static void SetTextProperty(Control c, string Value) 
    { 
     if (c.InvokeRequired) 
     { 
     SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty); 
     c.BeginInvoke(d, c, Value); 
     } 
     else 
     { 
     c.Text = Value; 
     } 
    } 
    } 

そして、あなたは、単に別のスレッドからSetAnyProperty()を使用することができます

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString()); 

この例では、上記のKvaserCanReaderクラスは独自のスレッドを実行し、メインフォーム上のlb_Speedラベルのtextプロパティを設定するための呼び出しを行います。

2

結果をUIスレッドに送信する場合は、同期コンテキストを使用します。私はスレッドプールのスレッド(コメントアウトされたコード)を使用することから変更し、私自身の新しいスレッドを作成するようにスレッドの優先度を変更する必要がありました。私はまだ同期コンテキストを使用して、データベースのキャンセルが成功したかどうかを返すことができました。

関連する問題