Cleanest Way to Invoke Cross-Thread Events
我发现.NET事件模型使得我经常在一个线程上引发一个事件,然后在另一个线程上侦听该事件。我想知道将事件从后台线程封送到我的UI线程的最干净方法是什么。
基于社区的建议,我使用了以下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 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
} |
我在网上有一些代码。比其他建议要好得多。一定要检查一下。
样品用量:
1 2 3 4 5 6 7 8
| 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.0之前的版本,否则不要在这样的代码中显式创建简单的委托,因此可以使用:
1 2 3
| BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent),
sender,
args); |
我避开了多余的委托声明。
1 2 3 4 5 6 7 8 9
| 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委托,因此根本不需要重新声明一个。
我出于个人目的制作了以下"通用"跨线程调用类,但我认为值得分享:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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():
1
| CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed,"Text", KvaserCanReader.GetSpeed.ToString()); |
在此示例中,上面的KvaserCanReader类运行其自己的线程并进行调用以设置主窗体上lb_Speed标签的text属性。
如果要将结果发送到UI线程,请使用同步上下文。我需要更改线程优先级,因此我从使用线程池线程(注释掉代码)开始更改,并创建了自己的新线程。我仍然能够使用同步上下文返回数据库取消是否成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #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());
}
} |
我认为最干净的方法肯定是走AOP路线。进行一些说明,添加必要的属性,您无需再次检查线程亲和力。
作为一个有趣的注释,WPF的绑定会自动处理封送处理,因此您可以将UI绑定到在后台线程上修改的对象属性,而无需执行任何特殊操作。事实证明,这对我来说是一个很好的节省时间。
在XAML中:
1
| <TextBox Text="{Binding Path=Name}"/> |
我一直想知道总是假设需要调用是多么昂贵...
1 2 3 4
| private void OnCoolEvent(CoolObjectEventArgs e)
{
BeginInvoke((o,e) => /*do work here*/,this, e);
} |
您可以尝试开发某种通用组件,该组件接受SynchronizationContext作为输入并使用它来调用事件。
|