关于c#:调用跨线程事件的最简单方法

关于c#:调用跨线程事件的最简单方法

Cleanest Way to Invoke Cross-Thread Events

我发现.NET事件模型使得我经常在一个线程上引发一个事件,然后在另一个线程上侦听该事件。我想知道将事件从后台线程封送到我的UI线程的最干净方法是什么。

基于社区的建议,我使用了以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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
}

我在网上有一些代码。比其他建议要好得多。一定要检查一下。

样品用量:

1
2
3
4
5
6
7
8
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.0之前的版本,否则不要在这样的代码中显式创建简单的委托,因此可以使用:
1
2
3
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent),
               sender,
               args);
  • 此外,您不需要创建和填充对象数组,因为args参数是" params"类型,因此您只需传递列表即可。

  • 相对于BeginInvoke,我可能更喜欢Invoke,因为后者会导致代码被异步调用,这可能是您所追求的,也可能不是,但是会使后续异常难以传播,而无需调用EndInvoke。将会发生的是,您的应用最终将获得TargetInvocationException


我避开了多余的委托声明。

1
2
3
4
5
6
7
8
9
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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():

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    #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());

        }
    }

我认为最干净的方法肯定是走AOP路线。进行一些说明,添加必要的属性,您无需再次检查线程亲和力。


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

在XAML中:

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


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

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

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


推荐阅读