2008-08-22 69 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 2009-04-29 17:30:21

回答

24

一对夫妇的意见:

  • 不要创建简单的代表明确这样的代码,除非你是2.0以上,所以你可以使用:
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
       sender, 
       args); 
  • 此外,您不需要创建和填充对象数组,因为args参数是“params”类型,因此您只需传入列表即可。

  • 我可能会倾向于Invoke超过BeginInvoke因为后者会导致代码被异步调用这可能是也可能不是你追求的,但会使处理后续异常困难,但无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

编辑:此外,每个事件都有相应的EventHandler委托,因此根本不需要重新声明一个委托。

+1

对我来说,它是这样工作的:`Invoke(new Action (mCoolObject_CoolEvent),sender,args);` – 2015-08-18 16:28:06

+0

@ToniAlmeida是的,这是我的代码中的拼写错误。感谢您指出。 – 2015-08-18 17:07:40

0

我一直想知道它是多么昂贵的总是假设,要求调用...

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

在GUI线程中执行BeginInvoke会导致有问题的操作被推迟,直到下一次UI线程处理Windows消息。在某些情况下,这实际上可能是有用的。 – supercat 2011-04-17 22:02:54

0

您可以尝试开发一些那种接受SynchronizationContext作为输入,并使用它来调用事件的通用组件。

2

作为一个有趣的方面说明,WPF的绑定自动处理封送处理,因此您可以将UI绑定到在后台线程上修改的对象属性,而无需执行任何特殊操作。这已经证明对我来说是一个很好的节省时间。

在XAML:

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

这不会工作。一旦你在非UI线程上设置道具,你会得到例外。即:Name =“gbc”砰!失败......没有免费的奶酪队友 – 2011-11-23 11:40:22

+0

这不是免费的(它花费执行时间),但是wpf绑定机器确实似乎自动处理跨线程编组。我们使用了很多道具,这些道具通过后台线程收到的网络数据进行更新。这里有一个解释:http://blog.lab49.com/archives/1166 – gbc 2011-11-27 21:43:30

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. 
    }); 
} 
3

我觉得最彻底的方法是绝对去AOP路线。创建几个方面,添加必要的属性,并且不必再次检查线程关联。

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线程,请使用同步上下文。我需要改变线程优先级,所以我改变了使用线程池线程(注释掉代码)并创建了自己的新线程。我仍然可以使用同步上下文来返回数据库是否成功取消。

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

     } 
    } 
相关问题