.NET调用流程

时间:2013-02-17 17:32:45

标签: .net invoke invokerequired

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

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

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

2 个答案:

答案 0 :(得分:4)

  

是否像中断...

不,一点也不。在繁忙执行代码时,没有安全的方法来中断线程。这导致了一种特别令人讨厌的问题,称为“重入错误”。这是固件程序员在嵌入式系统上实现中断处理程序时所遇到的一种错误。关于this web page中的一些背景知识。

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

Winforms为此方案(调用队列)添加了一个额外的队列。其中存储了代码创建的委托对象以及您提供的参数。 Begin / Invoke为调用队列添加一个条目,并对PostMessage()进行pinvoke,让UI线程知道需要完成某些事情。

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

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

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

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

答案 1 :(得分:2)

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

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

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

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

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

然后,Invoke调用marshaler.MarshaledInvoke并将委托作为参数执行。

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

将新任务排入与syncSameThread关联的队列中。

然后,如果marshalersyncSameThread,则调用true,在当前线程中执行当前控件的任务队列中的所有任务(此处为InvokeMarshaledCallbacks })。