发布于 2013-02-05 17:15:23
这个问题的答案在于C#控件是如何工作的
Windows窗体中的
控件绑定到特定的线程,并且不是线程安全的。因此,如果从不同的线程调用控件的方法,则必须使用该控件的某个invoke方法将调用封送到适当的线程。此属性可用于确定是否必须调用invoke方法,这在您不知道哪个线程拥有控件时非常有用。
有效地,Invoke所做的是确保您所调用的代码发生在控件“存在”的线程上,从而有效地防止了跨线程异常。
从历史的角度来看,在.Net 1.1中,这实际上是允许的。这意味着你可以尝试从任何后台线程在"GUI“线程上执行代码,这基本上是可行的。有时它只会导致你的应用程序退出,因为你有效地中断了GUI线程,而它正在做其他事情。这是跨线程异常-想象一下,当图形用户界面正在绘制其他东西时,试图更新TextBox。
实际上,您正在中断队列,这可能会产生许多不可预见的后果。Invoke实际上是将您想要执行的操作放入该队列的一种“礼貌”方式,并且从.Net 2.0开始,通过抛出的InvalidOperationException强制执行此规则。
为了理解幕后实际发生的事情,以及"GUI线程“的含义,理解消息泵或消息循环是很有用的。
这实际上已经在问题"What is a Message Pump“中得到了回答,建议您阅读以了解在与控件交互时所绑定的实际机制。
你可能会发现其他有用的读物包括:
Windows GUI编程的基本规则之一是,只有创建控件的线程才能访问和/或修改其内容(少数有文档记录的例外情况除外)。尝试在任何其他线程中执行此操作,您将获得不可预测的行为,从死锁到异常,再到半更新的UI。然后,从另一个线程更新控件的正确方法是向应用程序消息队列发布适当的消息。当消息泵开始执行该消息时,控件将在创建它的同一线程上进行更新(请记住,消息泵在主线程上运行)。
并且,对于代码更多的概述和代表性样本:
Invalid Cross-thread Operations
// 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有所了解,您可能希望考虑使用扩展方法来包装这些调用。堆栈溢出问题Cleaning Up Code Littered with Invoke Required很好地解决了这一点。
还有一个可能感兴趣的更深层次的write up of what happened historically。
发布于 2013-02-05 17:13:20
Windows Forms中的控件或窗口对象只是由句柄(有时称为HWND)标识的Win32窗口的包装。您对该控件所做的大多数操作最终都会导致使用此句柄的Win32应用编程接口调用。句柄归创建它的线程(通常是主线程)所有,不应该被另一个线程操纵。如果出于某种原因,您需要对来自另一个线程的控件执行某些操作,您可以使用Invoke
来请求主线程代表您执行此操作。
例如,如果要更改工作线程中标签的文本,可以执行以下操作:
theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));
发布于 2013-02-05 17:20:31
如果要修改控件,则必须在创建该控件的线程中完成。此Invoke
方法允许您执行关联线程(拥有控件的基础窗口句柄的线程)中的方法。
在下面的示例中,由于SetText1试图修改来自另一个线程的textBox1.Text,thread1抛出了一个异常。但在thread2中,SetText2中的操作是在创建TextBox的线程中执行的
private void btn_Click(object sender, EvenetArgs e)
{
var thread1 = new Thread(SetText1);
var thread2 = new Thread(SetText2);
thread1.Start();
thread2.Start();
}
private void SetText1()
{
textBox1.Text = "Test";
}
private void SetText2()
{
textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}
https://stackoverflow.com/questions/14703698
复制相似问题