Invoke(Delegate)

本文关键字:Delegate Invoke | 更新日期: 2023-09-27 17:57:34

有人能解释一下写在这个链接上的声明吗

Invoke(Delegate):

在拥有控件的基础窗口句柄的线程上执行指定的委托

有人能解释一下这意味着什么吗(尤其是粗体)我无法清楚地理解

这个问题的答案在于C#控件如何工作

Windows窗体中的控件绑定到特定线程,而不是线程安全。因此,如果您从不同的线程,必须使用控件的调用方法之一将调用封送至适当的线程。此属性可用于确定是否必须调用invoke方法,如果您不知道哪个线程拥有一个控件。

来自控制。调用所需

实际上,Invoke所做的是确保您正在调用的代码发生在控件"所在"的线程上,从而有效地防止跨线程异常。

从历史的角度来看,在。净1.1,这实际上是允许的。它的意思是,您可以尝试从任何后台线程在"GUI"线程上执行代码,这将主要起作用。有时,这只会导致你的应用程序退出,因为你在做其他事情时有效地中断了GUI线程。这是跨线程异常-想象一下,当GUI正在绘制其他内容时,尝试更新TextBox。

  • 哪个行动优先
  • 两者同时发生有可能吗
  • GUI需要运行的所有其他命令会发生什么情况

实际上,您正在中断队列,这可能会产生许多不可预见的后果。Invoke实际上是一种"礼貌"的方式,可以将您想做的事情放入该队列,并且该规则是从开始强制执行的。Net 2.0向前通过抛出的InvalidOperationException。

要了解幕后实际发生的事情,以及"GUI线程"的含义,了解什么是消息泵或消息循环是很有用的

事实上,这已经在"什么是消息泵"的问题中得到了回答,建议阅读以了解与控件交互时所绑定的实际机制。

你可能会发现有用的其他读物包括:

开始调用有什么问题

Windows GUI编程的基本规则之一是创建控件的线程可以访问和/或修改其内容(除了少数记录在案的例外情况)。试着从其他地方开始线程,您将得到不可预测的行为,从死锁到更新了一半的UI的异常。然后更新来自另一个线程的控件是向应用程序消息队列。当消息泵到达执行该消息时,控件将在创建它的线程(记住,消息泵在主线程上运行螺纹)。

以及,对于具有代表性的示例的代码量更大的概述:

无效的跨线程操作

// 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,您可能希望考虑使用扩展方法来包装这些调用。这在堆栈溢出问题"清理需要调用的代码"中有很好的介绍。

还有一篇关于历史上发生的事情的进一步报道可能会引起人们的兴趣。

Invoke(Delegate)

Windows窗体中的控件或窗口对象只是由句柄(有时称为HWND)标识的Win32窗口的包装。您对控件所做的大多数操作最终都会导致使用此句柄的Win32 API调用。句柄由创建它的线程(通常是主线程)所有,不应由另一个线程操作。如果出于某种原因,您需要对来自另一个线程的控件执行某些操作,则可以使用Invoke请求主线程代表您执行。

例如,如果你想更改工作线程中标签的文本,你可以这样做:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

如果要修改控件,必须在创建控件的线程中完成。此Invoke方法允许您在关联的线程(拥有控件的底层窗口句柄的线程)中执行方法。

在下面的示例线程1中,由于SetText1正试图修改另一个线程的textBox1.Text,因此引发异常。但是在thread2中,SetText2中的Action在创建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"));
}
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

实际上,这意味着委托保证在主线程上被调用。这一点很重要,因为在windows控件的情况下,如果你不在主线程上更新它们的属性,那么你要么看不到更改,要么控件引发异常。

模式是:

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)
}

this.Invoke(delegate)确保在主线程/创建的线程上调用委托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;
        }
    }

在某些情况下,尽管您创建了一个线程池线程(即工作线程),但它将在主线程上运行。它不会创建新的线程,因为主线程可用于处理进一步的指令。因此,首先使用this.InvokeRequired调查当前正在运行的线程是否为主线程,如果返回true,则当前代码正在工作线程上运行,因此调用这Invoke(d,new object[]{text});

否则直接更新UI控件(这里保证您在主线程上运行代码。)

这意味着委托将在UI线程上运行,即使您从后台工作线程或线程池线程调用该方法。UI元素具有线程亲和性-它们只喜欢直接与一个线程对话:UI线程。UI线程被定义为创建控件实例的线程,因此与窗口句柄相关联。但所有这些都是一个实现细节。

关键是:您可以从工作线程调用此方法,这样您就可以访问UI(更改标签中的值等),因为不允许您从UI线程以外的任何其他线程执行此操作。

委托本质上是内联ActionFunc<T>。您可以在正在运行或使用lambda表达式(=>)的方法的范围之外声明委托;因为您在方法中运行委托,所以您在为当前窗口/应用程序运行的线程上运行它,该线程为粗体。

Lambda示例

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

这意味着您传递的委托在创建Control对象的线程(即UI线程)上执行。

当你的应用程序是多线程的,并且你想从UI线程以外的线程执行一些UI操作时,你需要调用这个方法,因为如果你只是试图从不同的线程调用控件上的方法,你会得到一个系统。InvalidOperationException。