调用(委托)

时间:2013-02-05 09:09:02

标签: c# winforms delegates invoke

任何人都可以解释这个link

上写的声明
Invoke(Delegate):

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

任何人都可以解释这意味着什么(特别是大胆的)我无法清楚地了解它

9 个答案:

答案 0 :(得分:120)

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

  

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

来自Control.InvokeRequired

实际上,Invoke所做的是确保您调用的代码出现在控件“依赖”的线程上,从而有效地防止了交叉线程异常。

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

  • 哪项行动优先?
  • 两者都可以立刻发生吗?
  • GUI需要运行的所有其他命令会发生什么?

实际上,你正在准备一个队列,这可能会产生很多不可预见的后果。调用实际上是将您想要做的事情放入该队列的“礼貌”方式,并且此规则是通过抛出的InvalidOperationException从.Net 2.0开始实施的。

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

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

您可能会发现有用的其他阅读内容包括:

What's up with Begin Invoke

  

Windows GUI编程的一个基本规则是只有   创建控件的线程可以访问和/或修改其内容   (除少数记录的例外情况外)。尝试从其他任何地方做   线程,你会得到不可预知的行为,从死锁到   半更新UI的例外情况。正确的方式然后更新一个   来自另一个线程的控制是将相应的消息发布到   应用程序消息队列当消息泵到达时   执行该消息,控件将更新,同样   创建它的线程(记住,消息泵在main上运行   线程)。

并且,对于具有代表性样本的更多代码重要概述:

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有所了解,您可能希望考虑使用扩展方法来包装这些调用。 Stack Overflow问题Cleaning Up Code Littered with Invoke Required中详细介绍了这一点。

还有一些可能感兴趣的write up of what happened historically

答案 1 :(得分:62)

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

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

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

答案 2 :(得分:22)

如果要修改控件,则必须在创建控件的线程中完成。这个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"));
}

答案 3 :(得分:5)

Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

答案 4 :(得分:2)

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

答案 5 :(得分:2)

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

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

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:0)

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

Lambda示例

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

答案 8 :(得分:0)

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

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