这个问题的答案在于 C# 控件的工作原理
Windows 窗体中的控件绑定到特定线程,而不是
线程安全。因此,如果您从
不同的线程,您必须使用控件的调用方法之一
将调用编组到正确的线程。该属性可用于
确定是否必须调用调用方法,这在以下情况下很有用:
你不知道哪个线程拥有一个控件。
From Control.InvokeRequired http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired%28v=vs.90%29.aspx
实际上,Invoke 的作用是确保您调用的代码发生在控件“生存”的线程上,从而有效防止跨线程异常。
从历史的角度来看,在.Net 1.1中,这实际上是允许的。这意味着您可以尝试从任何后台线程在“GUI”线程上执行代码,这通常会起作用。有时它只会导致您的应用程序退出,因为您在 GUI 线程正在执行其他操作时有效地中断了它。这是跨线程异常- 想象一下当 GUI 正在绘制其他内容时尝试更新 TextBox。
- 哪个行动优先?
- 两者有可能同时发生吗?
- GUI 需要运行的所有其他命令会发生什么情况?
实际上,您正在中断队列,这可能会产生许多不可预见的后果。 Invoke 实际上是一种“礼貌”的方式,可以将您想要执行的操作放入队列中,并且从 .Net 2.0 开始,通过抛出异常强制执行此规则无效操作异常 http://msdn.microsoft.com/en-gb/library/system.invalidoperationexception.aspx.
要了解幕后实际发生的情况以及“GUI 线程”的含义,了解什么是消息泵或消息循环很有用。
这实际上已经在问题中得到了回答”什么是消息泵 https://stackoverflow.com/questions/2222365/what-is-a-message-pump”,建议阅读以了解与控件交互时所绑定的实际机制。
您可能会发现有用的其他阅读材料包括:
开始调用是怎么回事 http://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke
Windows GUI 编程的基本规则之一是,只有
创建控件的线程可以访问和/或修改其内容
(除了一些有记录的例外情况)。尝试从任何其他地方做
线程,你会得到不可预测的行为,从死锁到
更新一半的 UI 除外。那么更新的正确方法
来自另一个线程的控制是将适当的消息发布到
应用程序消息队列。当消息泵到达时
执行该消息,控件将在同一时间得到更新
创建它的线程(记住,消息泵在主线程上运行
线)。
并且,对于具有代表性示例的更多代码概述:
无效的跨线程操作 http://www.perceler.com/articles1.php?art=crossthreads1
// the canonical form (C# consumer)
public delegate void ControlStringConsumer(Control control, string text); // defines a delegate type
public void SetText(Control control, string text) {
if (control.InvokeRequired) {
control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text}); // invoking itself
} else {
control.Text=text; // the "functional part", executing only on the main thread
}
}
一旦您欣赏了 InvokeRequired,您可能希望考虑使用扩展方法来包装这些调用。 Stack Overflow 问题中巧妙地涵盖了这一点清理充斥着 Invoke required 的代码 https://stackoverflow.com/questions/3874134/cleaning-up-code-littered-with-invokerequired.
还有一个更进一步的写下历史上发生的事情 http://ikriv.com/dev/dotnet/MysteriousHang.html这可能令人感兴趣。