如何将事件回调变为我的win表单线程安全?

Sim*_*bee 38 .net c# events multithreading winforms

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

问题是,当调用回调时,您不能假设您可以对表单进行更新控制,因为如果在与运行表单的线程不同的线程上调用事件回调,则有时这些控件将引发异常.

Jak*_*son 34

为了简化Simon的代码,您可以使用内置的通用Action委托.它可以使您不需要的一堆委托类型节省您的代码.此外,在.NET 3.5中,他们为Invoke方法添加了一个params参数,因此您不必定义临时数组.

void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text = "Something happened";
}
Run Code Online (Sandbox Code Playgroud)


Sim*_*bee 17

以下是重点:

  1. 您不能从与其创建的线程不同的线程(表单的线程)进行UI控制调用.
  2. 委托调用(即事件挂钩)在与触发事件的对象相同的线程上触发.

因此,如果你有一个单独的"引擎"线程做一些工作,并有一些UI观察状态变化,可以反映在UI(如进度条或其他),你有一个问题.引擎触发了一个对象已更改的事件,该事件已被表单挂钩.但是,引擎在引擎中注册的回调委托在引擎的线程上被调用...而不是在Form的线程上.因此,您无法更新该回调中的任何控件.卫生署!

BeginInvoke来救援.只需在所有回调方法中使用这个简单的编码模型,你就可以确定事情会好起来的:

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";
}
Run Code Online (Sandbox Code Playgroud)

这真的很简单.

  1. 使用InvokeRequired查找是否在正确的线程上发生了此回调.
  2. 如果没有,则使用相同的参数重新调用正确线程上的回调.您可以使用Invoke(阻塞)或BeginInvoke(非阻塞)方法重新调用方法.
  3. 下次调用该函数时,InvokeRequired返回false,因为我们现在位于正确的线程上并且每个人都很高兴.

这是解决此问题的一种非常紧凑的方法,可以使您的表单免受多线程事件回调的影响.


Jas*_*ler 9

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

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}
Run Code Online (Sandbox Code Playgroud)