最干净的方式来调用跨线程事件

我发现.NET事件模型是这样的,我经常在一个线程上引发一个事件,并在另一个线程上监听它。 我想知道从后台线程到我的UI线程编组事件最干净的方法是什么。

根据社区的build议,我用了这个:

// 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 } 

几点意见:

  • 不要像这样的代码显式创build简单的委托,除非你是2.0以前,所以你可以使用:
  BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args); 
  • 你也不需要创build和填充对象数组,因为args参数是一个“params”types,所以你可以传入列表。

  • 我可能会喜欢Invoke over BeginInvoke因为后者会导致代码被asynchronous调用,这可能是也可能不是你想要的,但是如果没有调用EndInvoke处理后续的exception很难传播。 会发生什么是您的应用程序将最终得到一个TargetInvocationException

我有一些在线的代码 。 这比其他build议好得多。 绝对检查出来。

示例用法:

 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委托,所以根本不需要重新声明一个。

我认为最清洁的方式绝对是走AOP路线。 做几个方面,添加必要的属性,而不必再次检查线程关系。

作为有趣的一面,WPF的绑定自动处理封送处理,所以你可以将UI绑定到在后台线程上修改的对象属性,而不必做任何特殊的事情。 这已经certificate对我来说是一个很好的时间。

在XAML中:

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

我为了自己的目的做了下面的“通用”跨线程调用类,但我认为值得分享一下:

 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属性。

如果要将结果发送到UI线程,请使用同步上下文。 我需要改变线程优先级,所以我改变了使用线程池线程(注释掉代码),并创build了我自己的新线程。 我仍然能够使用同步上下文来返回数据库取消是否成功。

  #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()); } } 

您可以尝试开发某种接受SynchronizationContext作为input的通用组件,并使用它来调用事件。

我一直想知道如何总是假设需要调用…

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