5

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

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

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

4

2 回答 2

4

是不是像打断一样...

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

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

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

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

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

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

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

简单的答案是:method1 在当前线程(GUI 线程)中被调用。它非常类似于:

public void method1(Object sender, EventArgs args)
{
  if(dataGridView1.InvokeRequired)
    method1();
  else
    // Do something to dataGridView1
}

除了它还执行已在编组器控件中排队的所有先前方法。

这里有一些细节反编译Control.Invoke

正如 MSDN 上所解释的,Invoke“向上搜索控件的父链,直到找到具有窗口句柄的控件或表单”。我们称之为“父”控件:marshaler

然后,与委托一起Invoke调用以作为参数执行。marshaler.MarshaledInvoke

MarshaledInvoke中,执行的第一个操作是检查当前线程(调用的线程是否Invoke与附加到窗口句柄的线程相同marshaler。它将结果存储到变量syncSameThread中。

它将新任务排入与marshaler.

然后,如果syncSameThreadistrue它调用 in InvokeMarshaledCallbackswhich 在当前线程中执行当前控件的任务队列中的所有任务(这里是marshaler)。

于 2013-02-17T18:40:17.540 回答