关于c#:如何使事件回调进入Win窗体线程安全?

关于c#:如何使事件回调进入Win窗体线程安全?

How do I make event callbacks into my win forms thread safe?

当您从表单内订阅对象上的事件时,实际上是在将对回调方法的控制移交给事件源。您不知道该事件源是否会选择在其他线程上触发事件。

问题在于,调用回调时,您不能假定可以在表单上创建更新控件,因为有时如果在与运行表单的线程不同的线程上调用事件回调,则这些控件将引发异常开启。


要稍微简化Simon的代码,可以使用内置的通用Action委托。它节省了您不需要的大量委托类型,从而使您的代码更加实用。此外,在.NET 3.5中,他们向Invoke方法添加了params参数,因此您不必定义临时数组。

1
2
3
4
5
6
7
8
9
10
void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text ="Something happened";
}

以下是要点:

  • 您不能从不同于在其上创建线程(窗体线程)的线程进行UI控件调用。
  • 委托调用(即事件挂钩)在与触发事件的对象相同的线程上触发。
  • 因此,如果您有一个单独的"引擎"线程来做一些工作,并且有一些UI监视状态变化,这些变化可以反映在UI中(例如进度条或其他),那么您就遇到了问题。引擎触发的是一个对象更改事件,该事件已被Form挂钩。但是在引擎的线程上调用了向引擎注册的Form的回调委托。不在窗体的线程上。因此,您无法从该回调中更新任何控件。 h!

    BeginInvoke进行了救援。只要在所有回调方法中使用此简单的编码模型,就可以确保一切都会好起来:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private delegate void EventArgsDelegate(object sender, EventArgs ea);

    void SomethingHappened(object sender, EventArgs ea)
    {
       //
       // Make sure this callback is on the correct thread
       //
       if (this.InvokeRequired)
       {
          this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
          return;
       }

       //
       // Do something with the event such as update a control
       //
       textBox1.Text ="Something happened";
    }

    真的很简单。

  • 使用InvokeRequired查找此回调是否在正确的线程上发生。
  • 如果不是,则使用相同的参数在正确的线程上重新调用该回调。您可以使用Invoke(阻止)或BeginInvoke(非阻止)方法来重新调用方法。
  • 下次调用该函数时,InvokeRequired返回false,因为我们现在位于正确的线程上,每个人都很高兴。
  • 这是一种非常紧凑的方法,可以解决此问题并使表单免受多线程事件回调的影响。


    在这种情况下,我经常使用匿名方法:

    1
    2
    3
    4
    5
    void SomethingHappened(object sender, EventArgs ea)
    {
       MethodInvoker del = delegate{ textBox1.Text ="Something happened"; };
       InvokeRequired ? Invoke( del ) : del();
    }

    作为lazy programmer,我有一个非常懒惰的方法。

    我要做的就是这个。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private void DoInvoke(MethodInvoker del) {
        if (InvokeRequired) {
            Invoke(del);
        } else {
            del();
        }
    }
    //example of how to call it
    private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
        DoInvoke(delegate { lbl.Text = val; });
    }

    您可以在函数中内联DoInvoke或将其隐藏在单独的函数中,以为您做脏工作。

    请记住,您可以将函数直接传递到DoInvoke方法中。

    1
    2
    3
    4
    5
    6
    private void directPass() {
        DoInvoke(this.directInvoke);
    }
    private void directInvoke() {
        textLabel.Text ="Directly passed.";
    }

    我对这个话题有些迟了,但是您可能想看看基于事件的异步模式。正确实施后,可以确保始终从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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Forms;

    namespace WindowsFormsApplication1
    {
        public class MainForm : Form
        {
            private TypeWithAsync _type;

            [STAThread()]
            public static void Main()
            {
                Application.EnableVisualStyles();
                Application.Run(new MainForm());
            }

            public MainForm()
            {
                _type = new TypeWithAsync();
                _type.DoSomethingCompleted += DoSomethingCompleted;

                var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };

                var btn = new Button() { Text ="Synchronous" };
                btn.Click += SyncClick;
                panel.Controls.Add(btn);

                btn = new Button { Text ="Asynchronous" };
                btn.Click += AsyncClick;
                panel.Controls.Add(btn);

                Controls.Add(panel);
            }

            private void SyncClick(object sender, EventArgs e)
            {
                int value = _type.DoSomething();
                MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
            }

            private void AsyncClick(object sender, EventArgs e)
            {
                _type.DoSomethingAsync();
            }

            private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
            {
                MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
            }
        }

        class TypeWithAsync
        {
            private AsyncOperation _operation;

            // synchronous version of method
            public int DoSomething()
            {
                Thread.Sleep(5000);
                return 27;
            }

            // async version of method
            public void DoSomethingAsync()
            {
                if (_operation != null)
                {
                    throw new InvalidOperationException("An async operation is already running.");
                }

                _operation = AsyncOperationManager.CreateOperation(null);
                ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
            }

            // wrapper used by async method to call sync version of method, matches WaitCallback so it
            // can be queued by the thread pool
            private void DoSomethingAsyncCore(object state)
            {
                int returnValue = DoSomething();
                var e = new DoSomethingCompletedEventArgs(returnValue);
                _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
            }

            // wrapper used so async method can raise the event; matches SendOrPostCallback
            private void RaiseDoSomethingCompleted(object args)
            {
                OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
            }

            private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
            {
                var handler = DoSomethingCompleted;

                if (handler != null) { handler(this, e); }
            }

            public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
        }

        public class DoSomethingCompletedEventArgs : EventArgs
        {
            private int _value;

            public DoSomethingCompletedEventArgs(int value)
                : base()
            {
                _value = value;
            }

            public int Value
            {
                get { return _value; }
            }
        }
    }

    在许多简单的情况下,您可以使用MethodInvoker委托,而无需创建自己的委托类型。


    推荐阅读

      确定java按钮响应事件的代码

      确定java按钮响应事件的代码,,* 阅读本文可以结合最后在java登录窗口界面下面是一个链接。 是定义的容器。 容器(CP =得到内容面板); / /

      90后瓶门事件图片

      90后瓶门事件图片,,现在孩子们还太早,有些女孩子总是在门口等一会儿,最近发生了一次酒瓶门事件。 近日,山东一家寄宿学校的一名女生带着一瓶

      为什么WPS表单总是不打开

      为什么WPS表单总是不打开,,为什么WPS表格总是打不开??百度知道为您找到了6条优质回答span>a,type:cluster">很有可能文件已经损坏。恢复

      laravel-admin|自定义表单与验证

      laravel-admin|自定义表单与验证,表单,场景,场景:很多时候,由于我们业务场景比较特殊,需要自定义表单,然后框架给我提供了对应表单组建!案列:以

      唐山打人事件视频源自哪里

      唐山打人事件视频源自哪里,华为,视频,唐山打人事件视频源自哪里唐山打人事件视频来源于哪里?为什么这个网友人敢于发出这个视频?不怕被威胁

      Win7系统怎么打开事件查看器?

      Win7系统怎么打开事件查看器?,查看器,事件, win7系统中有一个事件查看器,事件查看器是重要的系统管理软件,通过它可以了解到某项功能配置、

      苹果7爆炸事件真的吗|iphone爆炸事件

      苹果7爆炸事件真的吗|iphone爆炸事件,,苹果7爆炸事件真的吗两个标志性建筑 。“9·11事件”指的是美国东部时间2001年9月11日上午(北京时间

      VB里的mousemove事件举例

      VB里的mousemove事件举例,鼠标,事件,本文目录VB里的mousemove事件举例C# 怎么在mousemove事件中判断鼠标是否运动能够在窗体上触发MouseMo