クロススレッドイベントを呼び出す最もクリーンな方法
-
09-06-2019 - |
質問
.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
}
解決
いくつかの観察:
- 2.0 より前で以下を使用できる場合を除き、このようなコードで単純なデリゲートを明示的に作成しないでください。
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent),
sender,
args);
また、args パラメータは「params」タイプなので、オブジェクト配列を作成して設定する必要はなく、リストを渡すだけで済みます。
私はおそらく支持するだろう
Invoke
以上BeginInvoke
後者の場合、コードが非同期的に呼び出されることになるため、これはあなたが望んでいることであるかどうかはわかりませんが、次の例外を呼び出すことなく後続の例外を処理するのが困難になります。EndInvoke
. 。何が起こるかというと、アプリは最終的にTargetInvocationException
その代わり。
他のヒント
私は持っている これのためのコード オンライン。これは他の提案よりもはるかに優れています。ぜひチェックしてみてください。
使用例:
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.
});
}
私は冗長なデリゲート宣言を避けます。
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
.
編集:さらに、すべてのイベントには対応する EventHandler
デリゲートなので、再宣言する必要はまったくありません。
私は自分自身の目的のために次の「ユニバーサル」クロススレッド呼び出しクラスを作成しましたが、共有する価値があると思います。
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 プロパティを設定する呼び出しを行います。
最もクリーンな方法だと思います 絶対に AOP ルートに進みます。いくつかのアスペクトを作成し、必要な属性を追加すれば、スレッドのアフィニティを再度チェックする必要はありません。
結果を UI スレッドに送信する場合は、同期コンテキストを使用します。スレッドの優先順位を変更する必要があったので、スレッド プール スレッド (コメントアウトされたコード) の使用を変更し、独自の新しいスレッドを作成しました。同期コンテキストを使用して、データベースのキャンセルが成功したかどうかを返すことはできました。
#region SyncContextCancel
private SynchronizationContext _syncContextCancel;
/// <summary>
/// Gets the synchronization context used for UI-related operations.
/// </summary>
/// <value>The synchronization context.</value>
protected SynchronizationContext SyncContextCancel
{
get { return _syncContextCancel; }
}
#endregion //SyncContextCancel
public void CancelCurrentDbCommand()
{
_syncContextCancel = SynchronizationContext.Current;
//ThreadPool.QueueUserWorkItem(CancelWork, null);
Thread worker = new Thread(new ThreadStart(CancelWork));
worker.Priority = ThreadPriority.Highest;
worker.Start();
}
SQLiteConnection _connection;
private void CancelWork()//object state
{
bool success = false;
try
{
if (_connection != null)
{
log.Debug("call cancel");
_connection.Cancel();
log.Debug("cancel complete");
_connection.Close();
log.Debug("close complete");
success = true;
log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
}
}
catch (Exception ex)
{
log.Error(ex.Message, ex);
}
SyncContextCancel.Send(CancelCompleted, new object[] { success });
}
public void CancelCompleted(object state)
{
object[] args = (object[])state;
bool success = (bool)args[0];
if (success)
{
log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
}
}
どれくらいの費用がかかるのかいつも疑問に思っていたのですが、 いつも 呼び出しが必要であると仮定します...
private void OnCoolEvent(CoolObjectEventArgs e)
{
BeginInvoke((o,e) => /*do work here*/,this, e);
}
興味深い補足として、WPF のバインディングはマーシャリングを自動的に処理するため、特別なことをしなくても、バックグラウンド スレッドで変更されるオブジェクト プロパティに UI をバインドできます。これは私にとって大きな時間の節約になることがわかりました。
XAML の場合:
<TextBox Text="{Binding Path=Name}"/>
を受け入れるある種の汎用コンポーネントの開発を試みることができます。 同期コンテキスト を入力として使用し、イベントを呼び出すために使用します。