调用(委托)

Posted

技术标签:

【中文标题】调用(委托)【英文标题】:Invoke(Delegate) 【发布时间】:2013-01-20 03:41:08 【问题描述】:

谁能解释一下写在link上的这个声明

Invoke(Delegate):

在拥有控件的底层窗口句柄的线程上执行指定的委托。

谁能解释一下这是什么意思(尤其是粗体字)我看不清楚

【问题讨论】:

这个问题的答案与 Control.InvokeRequired 属性相关 - 请参阅 msdn.microsoft.com/en-us/library/… 【参考方案1】:

这个问题的答案在于 C# 控件的工作原理

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

来自Control.InvokeRequired

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

从历史的角度来看,在 .Net 1.1 中,这实际上是允许的。这意味着您可以尝试从任何后台线程在“GUI”线程上执行代码,这通常会起作用。有时它只会导致您的应用程序退出,因为您在执行其他操作时有效地中断了 GUI 线程。这是跨线程异常 - 想象一下在 GUI 绘制其他内容时尝试更新 TextBox。

哪个操作优先? 是否有可能同时发生这两种情况? GUI 需要运行的所有其他命令会发生什么变化?

实际上,您正在打断队列,这可能会产生许多无法预料的后果。 Invoke 实际上是将您想做的事情放入该队列的“礼貌”方式,并且该规则从 .Net 2.0 开始通过抛出的InvalidOperationException 强制执行。

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

这实际上已经在问题“What is a Message Pump”中得到解答,建议阅读以了解您在与控件交互时所绑定的实际机制。

您可能会觉得有用的其他阅读材料包括:

What's up with Begin Invoke

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。

【讨论】:

【参考方案2】:

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

例如,如果您想从工作线程更改标签的文本,您可以执行以下操作:

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

【讨论】:

你能解释一下为什么有人会做这样的事情吗? this.Invoke(() => this.Enabled = true);this 所指的东西肯定在当前线程中,对吧? @KyleDelaney,对象不在线程中,当前线程不一定是创建对象的线程。【参考方案3】:

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

在下面的示例中,thread1 引发异常,因为 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"));

【讨论】:

我真的很喜欢这种方法,它隐藏了代表的性质,但无论如何它都是不错的捷径。【参考方案4】:
Invoke((MethodInvoker)delegate textBox1.Text = "Test"; );

【讨论】:

人们在其他回复中建议的 System.Action 的使用仅适用于框架 3.5+,对于旧版本,这可以完美运行【参考方案5】:

实际上,这意味着保证在主线程上调用委托。这很重要,因为对于 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)

【讨论】:

【参考方案6】:

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当前代码正在工作线程上运行所以调用 this.Invoke(d, new object[] text );

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

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

Lambda 示例

int AddFiveToNumber(int number)

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

【讨论】:

【参考方案9】:

表示你传递的委托在创建Control对象的线程(即UI线程)上执行。

当您的应用程序是多线程的并且您希望从 UI 线程以外的线程执行一些 UI 操作时,您需要调用此方法,因为如果您只是尝试从不同的线程调用 Control 上的方法,您将会得到一个 System.InvalidOperationException。

【讨论】:

以上是关于调用(委托)的主要内容,如果未能解决你的问题,请参考以下文章

3.C#中的多重委托

C#图解教程 第十三章 委托

UIPopoverController 委托委托方法未被调用

C# 笔记——委托

C# 委托及各种写法

C#中通过委托来调用对象方法的基本过程