【问题标题】:Cleanest Way to Invoke Cross-Thread Events调用跨线程事件的最简洁方式
【发布时间】:2010-09-06 13:07:03
【问题描述】:

我发现 .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
}

【问题讨论】:

  • 请记住,当现有托管控件还没有非托管句柄时,InvokeRequired 可能会返回 false。在完全创建控制之前,您应该谨慎处理将引发的事件。

标签: c# multithreading events


【解决方案1】:

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

【讨论】:

  • 您还可以在扩展程序中将命名空间更改为System.Windows.Forms。这样您就可以避免每次需要时都添加 您的自定义命名空间
【解决方案2】:

几个观察:

  • 不要在这样的代码中显式创建简单的委托,除非您是 2.0 之前的版本,因此您可以使用:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • 另外,您不需要创建和填充对象数组,因为 args 参数是“params”类型,因此您只需传入列表即可。

  • 我可能更喜欢Invoke 而不是BeginInvoke,因为后者会导致代码被异步调用,这可能是也可能不是你所追求的,但如果没有致电EndInvoke。会发生的情况是,您的应用最终会获得 TargetInvocationException

【讨论】:

    【解决方案3】:

    我避免使用多余的委托声明。

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

    【讨论】:

      【解决方案4】:

      我出于自己的目的制作了以下“通用”跨线程调用类,但我认为值得分享:

      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 标签的文本属性。

      【讨论】:

        【解决方案5】:

        我认为最干净的方法是肯定走 AOP 路线。做几个方面,添加必要的属性,你再也不用检查线程亲和性了。

        【讨论】:

        • 我不明白你的建议。 C# 不是原生的面向方面的语言。您是否想到了一些模式或库来实现在幕后实现编组的方面?
        • 我使用 PostSharp,所以我在一个属性类中定义线程行为,然后在每个必须在 UI 线程上调用的方法前面使用 [WpfThread] 属性。
        【解决方案6】:

        如果您想将结果发送到 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());
        
                }
            }
        

        【讨论】:

          【解决方案7】:

          我一直想知道总是假设调用是必需的......

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

          【讨论】:

          • 在 GUI 线程中执行 BeginInvoke 将导致相关操作延迟到 UI 线程下一次处理 Windows 消息时。在某些情况下,这实际上是一件有用的事情。
          【解决方案8】:

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

          在 XAML 中:

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

          【讨论】:

          • 这行不通。一旦你在非 UI 线程上设置了道具,你就会得到异常.. 即 Name="gbc" 砰!失败......没有免费的奶酪伴侣
          • 它不是免费的(它会花费执行时间),但 wpf 绑定机制似乎可以自动处理跨线程编组。我们经常将它与由后台线程接收的网络数据更新的道具一起使用。这里有一个解释:blog.lab49.com/archives/1166
          • @gbc Aaaaand 解释消失了 404。
          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            我正在使用类似的东西

            Invoke((Action)(() =>
                    {
                        //your code
                    }));
            

            【讨论】:

            • 请在您的答案中添加上下文。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-08-10
            • 1970-01-01
            • 2019-12-02
            • 2018-10-05
            • 2019-04-07
            • 1970-01-01
            • 2022-01-21
            相关资源
            最近更新 更多