代码之家  ›  专栏  ›  技术社区  ›  Nick

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

  •  81
  • Nick  · 技术社区  · 17 年前

    我发现。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
    }
    
    10 回复  |  直到 17 年前
        1
  •  45
  •   Domenic    13 年前

    我有 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.
        });
    }
    
        2
  •  29
  •   Naser Asadi    12 年前

    以下是一些观察结果:

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

    • 我可能会赞成 Invoke 结束 BeginInvoke 因为后者将导致代码被异步调用,这可能是也可能不是您所追求的,但如果不调用,将使后续异常的处理难以传播 EndInvoke 。你的应用程序最终会得到一个 TargetInvocationException 相反。

        3
  •  12
  •   Konrad Rudolph    10 年前

    我避免重复的代表声明。

    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
  •  6
  •   TarPista    13 年前

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

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

    在这个例子中,上面的KvaseCanReader类运行自己的线程,并调用来设置主窗体上lb_Speed标签的文本属性。

        5
  •  3
  •   Dmitri Nesteruk    16 年前

    我认为最干净的方法是 肯定 走AOP路线。只需创建几个方面,添加必要的属性,就永远不必再次检查线程相关性。

        6
  •  3
  •   The Lonely Coder    10 年前

    如果要将结果发送到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
  •  2
  •   user1228 user1228    17 年前

    我一直想知道这有多贵 总是 假设调用是必需的。..

    private void OnCoolEvent(CoolObjectEventArgs e)
    {
      BeginInvoke((o,e) => /*do work here*/,this, e);
    }
    
        8
  •  2
  •   gbc    17 年前

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

    在XAML中:

    <TextBox Text="{Binding Path=Name}"/>
    
        9
  •  0
  •   On Freund    17 年前

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

        10
  •  -3
  •   mahirgul    4 年前

    我正在使用类似的东西

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