调用(委托)
任何人都可以解释这个链接上写的这个声明
Invoke(Delegate):
在拥有控件底层窗口句柄的线程上执行指定的委托。
有谁能解释一下这个意思(特别是那个大胆的)我不能清楚地说清楚
这个问题的答案在于C#控件的工作方式
Windows窗体中的控件绑定到特定的线程,并且不是线程安全的。 因此,如果您从其他线程调用控件的方法,则必须使用控件的一个invoke方法将调用编组到适当的线程。 这个属性可以用来确定你是否必须调用一个invoke方法,如果你不知道哪个线程拥有一个控件,这个方法会很有用。
从Control.InvokeRequired
实际上,Invoke所做的是确保您所调用的代码发生在控件所在的线程上,从而有效地防止了跨线程exception。
从历史的angular度来看,在.NET 1.1中,这实际上是允许的。 这意味着你可以尝试在任何后台线程的“GUI”线程上执行代码,这将主要工作。 有时候它会导致你的应用程序退出,因为你正在做其他事情时,正在有效地使GUI线程中断。 这是交叉线程exception – 想象在GUI正在绘制其他东西时尝试更新文本框。
- 哪个行动优先?
- 是否有可能同时发生?
- GUI需要运行的所有其他命令会发生什么?
实际上,你正在排队,这可能会产生许多不可预料的后果。 Invoke实际上是一种“礼貌”的方式,让你想要进入这个队列,而这个规则是通过一个抛出的InvalidOperationException从.Net 2.0开始执行的。
为了理解幕后实际发生了什么,以及“GUI线程”是什么意思,了解什么是消息泵或消息循环是有用的。
实际上,这个问题已经在“ 什么是消息泵 ”的问题中得到了回答,并且为了理解与控件交互时所绑定的实际机制,推荐阅读。
其他阅读您可能会发现有用的包括:
怎么了开始调用
Windows GUI编程的基本规则之一是只有创build控件的线程才能访问和/或修改其内容(除了less数logging的例外)。 尝试从任何其他线程做到这一点,你会得到不可预知的行为,从死锁,exception到一半更新的用户界面。 然后从另一个线程更新控件的正确方法是将适当的消息发送到应用程序消息队列。 当消息泵开始执行该消息时,控件将在创build该消息的同一个线程上得到更新(请记住,消息泵在主线程上运行)。
而对于一个代表性样本来说,更多的代码重要的概述:
无效的跨线程操作
// 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有所了解,您可能希望考虑使用扩展方法来打包这些调用。 这是堆栈溢出问题巧妙地涵盖清理代码需要调用 。
还有更多的历史logging ,可能是有趣的。
Windows窗体中的控件或窗口对象只是一个由句柄 (有时称为HWND)标识的Win32窗口的包装。 使用该控件执行的大多数事情最终都会导致使用此句柄的Win32 API调用。 句柄由创build它的线程(通常是主线程)拥有,不应该被另一个线程操纵。 如果由于某种原因需要使用另一个线程的控制function,可以使用Invoke
让主线程以您的名义来完成。
例如,如果你想从一个工作线程改变一个标签的文本,你可以这样做:
theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));
如果要修改控件,则必须在创build控件的线程中完成。 这个Invoke
方法允许你在关联的线程(拥有该控件的底层窗口句柄的线程)中执行方法。
在下面的示例中,thread1引发exception,因为SetText1正试图从另一个线程修改textBox1.Text。 但在thread2中,SetText2中的Action在创buildTextBox的线程中执行
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")); }
Invoke((MethodInvoker)delegate {textBox1.Text =“Test”;});
实际上这意味着委托保证在主线程上被调用。 这一点很重要,因为在Windows控件的情况下,如果你没有在主线程上更新它们的属性,那么你要么看不到变化,要么控件引发exception。
模式是:
void OnEvent(object sender, EventArgs e) { if (this.InvokeRequired) { this.Invoke(() => this.OnEvent(sender, e); return; } // do stuff (now you know you are on the main thread) }
这意味着委托将在UI线程上运行,即使您从后台工作者或线程池线程中调用该方法。 UI元素具有线程相关性 – 他们只喜欢直接与一个线程交谈:UI线程。 UI线程被定义为创build控件实例的线程,因此与窗口句柄相关联。 但所有这些都是一个实现细节。
关键的一点是:你可以从一个工作者线程调用这个方法,这样你就可以访问UI(改变标签中的值等),因为你不能从UI线程以外的任何其他线程来做到这一点。
this.Invoke(delegate)
确保你正在主线程/创build的线程调用委托参数this.Invoke()
。
我可以说,除了主线程外,一个Thumb规则不会访问你的表单控件。
可能是以下几行对于使用Invoke()
private void SetText(string text) { // InvokeRequired required compares the thread ID of the // calling thread to the thread ID of the creating thread. // If these threads are different, it returns true. if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } }
尽pipe你创build一个线程池线程(也就是工作线程),但有些情况会在主线程上运行。 它不会创build一个新的线程因为主线程可用于处理进一步的指令。 所以首先调查当前正在运行的线程是否是主线程使用this.InvokeRequired
如果返回true当前代码正在工作线程上运行,所以调用this.Invoke(d,new object [] {text});
否则直接更新UI控件(这里保证你正在主线程上运行代码)
委托本质上是内联的Action
或Func<T>
。 您可以在您正在运行的方法的范围之外或使用lambda
expression式( =>
)声明委托。 因为你在一个方法中运行委托,所以你在当前正在运行的窗口/应用程序的粗体中运行它。
Lambda示例
int AddFiveToNumber(int number) { var d = (int i => i + 5); d.Invoke(number); }
这意味着您传递的委托在创buildControl对象的线程上执行(这是UI线程)。
当你的应用程序是multithreading的,并且你想从UI线程以外的线程执行一些UI操作时,你需要调用这个方法,因为如果你只是尝试从一个不同的线程调用一个控件的方法,你会得到一个System.InvalidOperationException。