如何在目标线程上运行事件处理程序

时间:2017-10-06 07:03:46

标签: c# multithreading winforms events

鉴于 C#代码示例:

using System;
using System.Threading;
using System.Windows.Forms;

public class MnFrm : Form
{
   private void MnFrm_Load(Object sender, EventArgs e)
   {
      this.WorkCompleted += MnFrm_WorkCompleted;
   }

   private void btn_Click(Object sender, EventArgs e)
   {
      ThreadPool.QueueUserWorkItem(AsyncMethod);
   }

   private void MnFrm_WorkCompleted(Object sender, Boolean e)
   {
      MessageBox.Show("Work completed");
   }

   private void AsyncMethod(Object state)
   {
      // Do stuff
      Boolean result = true; // just as an example
      WorkCompleted?.Invoke(this, result);
   }

   private event EventHandler<Boolean> WorkCompleted;
}

当用户单击按钮btn时,方法AsyncMethod将在ThreadPool管理的另一个线程上执行。一段时间后,工作完成,结果通过另一个事件回发。 此事件处理程序(WorkCompleted)在用于运行AsyncMethod的线程上执行,因为当应用程序执行时,您将获得“交叉线程”&#39;异常。

所以问题是如何在UI线程上运行事件处理程序MnFrm_WorkCompleted

3 个答案:

答案 0 :(得分:2)

您可以使用Control.Invoke或Control.BeginInvoke方法在UI线程上调用特定方法。 请尝试以下代码:

private void MnFrm_WorkCompleted(Object sender, Boolean e)
{
    if (InvokeRequired)
    {
       Invoke((Action) (() => MnFrm_WorkCompleted(sender, e)));
       return;
    }
    MessageBox.Show("Work completed");
}

对于Invoke和BeginInvoke之间的区别: What's the difference between Invoke() and BeginInvoke()

答案 1 :(得分:0)

如果您将其更改为:

this.Invoke(new Action(() =>
{
    WorkCompleted?.Invoke(this, result);
});

它会起作用。这是因为Form包含一个方法Invoke(),它将在创建它的线程上调用该方法。事件的调用只不过是调用代理。

阅读here表单moet info。

您可以使用InvokeRequired来确定您是否使用了正确的主题,但我认为它的开销会降低其可读性。始终使用this.Invoke

BeginInvoke,这在发布&#39;时非常有用。给UI线程的消息。唯一的问题是,当线程引发许多事件并且UI线程没有足够的时间来处理它们时,您无法查看有多少事件排队。

第三种方法是使用队列。当事件发生时,将消息添加到队列(我将使用List<>并使用表单计时器来处理队列。优点是线程不会被UI线程停止,并在将其添加到队列后直接继续。而你的申请将被停滞。

答案 2 :(得分:0)

全部谢谢!

Jeroen van Langen 的答案非常正确!有效。实际上我是这样做的,但不想发布解决方案以防止偏见 - 我希望看到替代解决方案。

但是,我更喜欢 Panagiotis Kanavos 的答案,他建议使用var result=await Task.Run(...)。这美妙地清理了代码!谢谢 Panagiotis Kanavos !!!