.NET调用流程

poy*_*poy 5 .net invoke invokerequired

我一直看到/使用代码的某种形式或方式:

public void method1(Object sender, EventArgs args)
{
  if(dataGridView1.InvokeRequired)
    dataGridView1.Invoke(new EventHandler(method1), null);
  else
    // Do something to dataGridView1
}
Run Code Online (Sandbox Code Playgroud)

我的问题是......当我使用时,GUI线程会发生什么Invoke?它是否像一个中断,线程将立即执行method1

Han*_*ant 4

是不是像中断一样...

一点都不。当线程忙于执行代码时,没有安全的方法可以中断该线程。这会导致一种特别令人讨厌的问题,称为“重入错误”。这是固件程序员在嵌入式系统上实现中断处理程序时要面对的错误。本网页中有一些相关背景信息。

程序的 UI 线程以不同的方式解决了这个问题,它在生产者-消费者问题的标准解决方案中扮演消费者的角色。成分是生产者将消息发布到的线程安全队列和消费者线程中的调度程序循环。它从队列中检索消息并执行与该消息关联的消息处理程序。这通常被描述为“泵送消息循环”。生产者最常见的是操作系统,为用户按下按键或移动鼠标等事件生成消息。但它可以是生成消息的任何代码,包括另一个线程。

Winforms 向该方案添加了一个附加队列,即调用队列。它存储您的代码创建的委托对象以及您提供的参数。Begin/Invoke 将一个条目添加到调用队列并调用 PostMessage() 以让 UI 线程知道需要执行某些操作。

如果 UI 线程正忙于执行代码(例如处理绘制事件),那么它就会忽略这一点。它不会注意到已发布的消息,直到它再次空闲,重新进入调度程序循环并调用 GetMessage()。或者它已经空闲了,它会很快回复消息。它检索调用队列中的条目并执行委托目标。

如果使用 Invoke 而不是 BeginInvoke,它将调用队列条目中 ManualResetEvent 的 Set() 方法。您的线程正在等待,然后它将恢复执行。如果委托方法失败,那么此时引发的异常也将在线程中重新引发。

您可以从其工作方式中得出一些基本结论:

  • 线程恢复执行的速度完全取决于 UI 线程的繁忙程度
  • 一般来说,您希望支持 BeginInvoke,因为这可以防止工作线程阻塞
  • Begin/Invoke调用自动序列化,先进先出,不需要额外的锁定
  • 但是,如果您使用 BeginInvoke,则委托要稍后才会运行,因此您必须确保提供给委托目标方法的任何数据在其运行时仍然有效。这可能需要锁定
  • 使用 Invoke 而不是 BeginInvoke 可能会导致死锁。当 UI 线程空闲而是等待其他事情发生时,就会发生这种情况。当其他东西正在等待您的线程完成时,肯定会出现死锁。另一个支持 BeginInvoke 而不是 Invoke 的好理由
  • 当您的调用速度快于 UI 线程执行委托目标的速度时,您就会遇到问题。UI 线程永远无法跟上并且调用队列无限增长。很容易注意到这一点,UI 线程停止处理低优先级的任务。其中包括绘画,你的 UI 看起来会冻结
  • 当您的线程尝试调用已处理的表单或控件时,您将遇到大问题。当用户关闭窗口并且您不确保工作线程停止运行时,通常会发生这种情况。这通常会导致崩溃,ObjectDisposeException 是最常见的结果