调用跨线程事件的最简洁方法

时间:2022-12-06 10:58:45

I find that the .NET event model is such that I'll often be raising an event on one thread and listening for it on another thread. I was wondering what the cleanest way to marshal an event from a background thread onto my UI thread is.

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

Based on the community suggestions, I've used this:

根据社区建议,我用过这个:

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

9 个解决方案

#1


25  

A couple of observations:

几点意见:

  • Don't create simple delegates explicitly in code like that unless you're pre-2.0 so you could use:
  • 不要在类似的代码中显式创建简单的委托,除非你是pre-2.0所以你可以使用:

   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Also you don't need to create and populate the object array because the args parameter is a "params" type so you can just pass in the list.

    此外,您不需要创建和填充对象数组,因为args参数是“params”类型,因此您只需传入列表即可。

  • I would probably favor Invoke over BeginInvoke as the latter will result in the code being called asynchronously which may or may not be what you're after but would make handling subsequent exceptions difficult to propagate without a call to EndInvoke. What would happen is that your app will end up getting a TargetInvocationException instead.

    我可能更喜欢Invoke而不是BeginInvoke,因为后者将导致代码被异步调用,这可能会或可能不会是你所追求的但是如果没有调用EndInvoke就会使后续异常难以传播。会发生什么是您的应用程序将最终获得TargetInvocationException。

#2


41  

I have some code for this online. It's much nicer than the other suggestions; definitely check it out.

我在网上有一些代码。它比其他建议好得多;绝对检查一下。

Sample usage:

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


11  

I shun redundant delegate declarations.

我避免冗余的代表声明。

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
}

For non-events, you can use the System.Windows.Forms.MethodInvoker delegate or System.Action.

对于非事件,您可以使用System.Windows.Forms.MethodInvoker委托或System.Action。

EDIT: Additionally, every event has a corresponding EventHandler delegate so there's no need at all to redeclare one.

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

#4


3  

I think the cleanest way is definitely to go the AOP route. Make a few aspects, add the necessary attributes, and you never have to check thread affinity again.

我认为最干净的方法肯定是走AOP路线。制作一些方面,添加必要的属性,您再也不必检查线程关联。

#5


3  

I made the following 'universal' cross thread call class for my own purpose, but I think it's worth to share it:

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

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

And you can simply use SetAnyProperty() from another thread:

你可以简单地从另一个线程使用SetAnyProperty():

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

In this example the above KvaserCanReader class runs its own thread and makes a call to set the text property of the lb_Speed label on the main form.

在此示例中,上面的KvaserCanReader类运行自己的线程并调用以在主窗体上设置lb_Speed标签的text属性。

#6


3  

Use the synchronisation context if you want to send a result to the UI thread. I needed to change the thread priority so I changed from using thread pool threads (commented out code) and created a new thread of my own. I was still able to use the synchronisation context to return whether the database cancel succeeded or not.

如果要将结果发送到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  

As an interesting side note, WPF's binding handles marshaling automatically so you can bind the UI to object properties that are modified on background threads without having to do anything special. This has proven to be a great timesaver for me.

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

In XAML:

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

#8


1  

I've always wondered how costly it is to always assume that invoke is required...

我一直想知道总是假设需要调用是多么昂贵......

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

#9


0  

You can try to develop some sort of a generic component that accepts a SynchronizationContext as input and uses it to invoke the events.

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

#1


25  

A couple of observations:

几点意见:

  • Don't create simple delegates explicitly in code like that unless you're pre-2.0 so you could use:
  • 不要在类似的代码中显式创建简单的委托,除非你是pre-2.0所以你可以使用:

   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Also you don't need to create and populate the object array because the args parameter is a "params" type so you can just pass in the list.

    此外,您不需要创建和填充对象数组,因为args参数是“params”类型,因此您只需传入列表即可。

  • I would probably favor Invoke over BeginInvoke as the latter will result in the code being called asynchronously which may or may not be what you're after but would make handling subsequent exceptions difficult to propagate without a call to EndInvoke. What would happen is that your app will end up getting a TargetInvocationException instead.

    我可能更喜欢Invoke而不是BeginInvoke,因为后者将导致代码被异步调用,这可能会或可能不会是你所追求的但是如果没有调用EndInvoke就会使后续异常难以传播。会发生什么是您的应用程序将最终获得TargetInvocationException。

#2


41  

I have some code for this online. It's much nicer than the other suggestions; definitely check it out.

我在网上有一些代码。它比其他建议好得多;绝对检查一下。

Sample usage:

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


11  

I shun redundant delegate declarations.

我避免冗余的代表声明。

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
}

For non-events, you can use the System.Windows.Forms.MethodInvoker delegate or System.Action.

对于非事件,您可以使用System.Windows.Forms.MethodInvoker委托或System.Action。

EDIT: Additionally, every event has a corresponding EventHandler delegate so there's no need at all to redeclare one.

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

#4


3  

I think the cleanest way is definitely to go the AOP route. Make a few aspects, add the necessary attributes, and you never have to check thread affinity again.

我认为最干净的方法肯定是走AOP路线。制作一些方面,添加必要的属性,您再也不必检查线程关联。

#5


3  

I made the following 'universal' cross thread call class for my own purpose, but I think it's worth to share it:

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

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

And you can simply use SetAnyProperty() from another thread:

你可以简单地从另一个线程使用SetAnyProperty():

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

In this example the above KvaserCanReader class runs its own thread and makes a call to set the text property of the lb_Speed label on the main form.

在此示例中,上面的KvaserCanReader类运行自己的线程并调用以在主窗体上设置lb_Speed标签的text属性。

#6


3  

Use the synchronisation context if you want to send a result to the UI thread. I needed to change the thread priority so I changed from using thread pool threads (commented out code) and created a new thread of my own. I was still able to use the synchronisation context to return whether the database cancel succeeded or not.

如果要将结果发送到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  

As an interesting side note, WPF's binding handles marshaling automatically so you can bind the UI to object properties that are modified on background threads without having to do anything special. This has proven to be a great timesaver for me.

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

In XAML:

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

#8


1  

I've always wondered how costly it is to always assume that invoke is required...

我一直想知道总是假设需要调用是多么昂贵......

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

#9


0  

You can try to develop some sort of a generic component that accepts a SynchronizationContext as input and uses it to invoke the events.

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