Invoke and BeginInvoke

19

您好,我正在使用C#开发应用程序。目前我在处理线程,有一个问题一直萦绕在我的脑海中。 Invoke和BeginInvoke有什么区别? 我阅读了一些帖子,在这里找到了一些有用的信息:here

但是在以下代码中,Invoke和BeginInvoke有什么区别:

private void ProcessRoutine()
{
   for (int nValue = StartFrom; nValue <= EndTo; nValue++)
   {
      this.Invoke(this.MyDelegate, nValue);
      //this.BeginInvoke(this.MyDelegate, nValue);
   }
   MessageBox.Show("Counting complete!");
}
private void MessageHandler(int progress)
{
    lblStatus.Text = lblStatus.Text = "Processing item: " + progress.ToString();
    progressBar1.Value = progress;
}

其中MyDelegate是指向MessageHandler函数的引用。

我注意到使用BeginInvoke时,lblStatus.Text没有刷新,而使用Invoke会刷新标签。此外,我知道Invoke会等待其执行完成。我最感兴趣的情况是为什么在这种情况下刷新标签文本会有差异。

5个回答

19

首先,来自您提供的链接:

  • Control.Invoke: 在UI线程上执行,但调用线程等待完成后才继续。
  • Control.BeginInvoke: 在异步UI线程上执行,且调用线程不会等待执行完成。

并且来自MSDN:

BeginInvoke在创建控件句柄的线程上异步地执行指定的委托。

总之,BeginInvoke异步的。当从UI线程调用BeginInvoke时,请求将与UI线程并行执行。这意味着它可能要在当前正在执行的方法返回后才会执行。因此,在这种情况下,文本框将永远不会更新,因为for循环不会被打断,调用线程在继续执行之前将不会等待此事件完成。

相反,Invoke同步的。文本框将被更新,因为调用线程将等待调用完成后再继续执行。


2
一个小细节 - 当方法返回时,它不会被执行,而是作为一种“点火并忘记”的方式完成 - 它被发射到COM/winapi以太中。 - Chris S

8

使用Invoke方法时,该方法将被执行,应用程序等待其完成。

使用BeginInvoke方法时,该方法将以异步方式调用,并且在使用BeginInvoke引用的方法执行时,应用程序继续执行。

使用BeginInvoke方法时,您需要调用EndInvoke方法才能获取使用BeginInvoke执行的方法的结果。

请勿在BeginXXX方法中更新GUI组件,因为它们在与GUI线程不同的线程中运行,与您的Invoke方法相反。您不能在与GUI线程不同的线程中访问GUI组件。

希望这可以帮助您!


13
调用BeginInvoke并不意味着它不在UI线程上执行。它意味着在与“this”相关的线程上异步调用。这个线程可能是UI线程。 - Taylor Leese
8
从 MSDN 上的描述可以看出,该方法会在创建控件句柄的线程上异步执行指定的委托。因此,如果从 UI 线程调用该方法,则该委托将被放置在队列中,在 UI 线程空闲时执行。这通常是在当前正在执行的方法返回后。 - Justin Ethier
13
在BeginXXX方法中更新GUI组件是错误的,因为它们在GUI线程之外运行,与Invoke方法相反。这是错误的,应进行更正,因为这可能会误导他人(此外,这是被接受的答案...)。 - Otiel

7
Control.BeginInvoke不能在不同的线程(或线程池)上工作,但委托.BeginInvoke可以。MSDN的一句话说:
“异步地在创建控件基础句柄的线程上执行指定的委托。”
然而,Control.BeginInvoke只是使用PostMessage并返回,没有CLR Thread被创建。
“PostMessage函数将消息放置(发布)到与创建指定窗口的线程相关联的消息队列中,并在等待线程处理消息之前返回。” 这篇文章很好地总结了何时使用InvokeBeginInvoke
“你要问哪个函数该用。这真的取决于你的需求。如果你想在继续之前完成UI更新,那么你使用Invoke。如果没有这样的要求,我建议使用BeginInvoke,因为它使调用它的线程看起来似乎更快。不过,BeginInvoke有一些陷阱。”
- 如果通过BeginInvoke调用的函数访问共享状态(在UI线程和其他线程之间共享的状态),那么你就会遇到麻烦。状态可能会在你调用BeginInvoke和包装函数实际执行之间发生改变,导致难以找到的时间问题。 - 如果你将引用参数传递给通过BeginInvoke调用的函数,则必须确保在函数完成之前没有其他人修改传递的对象。通常,人们在将其传递给BeginInvoke之前对对象进行克隆,从而完全避免了这个问题。

1

BeginInvoke 在另一个线程上执行方法体,并允许当前线程继续。如果您尝试直接从另一个线程更新控件属性,它将抛出异常。


1
是的,我知道,但为什么使用Invoke我的标签文本会被刷新,而使用BeginInvoke则不会。 - niao
@Niao,请看我回答的第二句话。 - Rex M

0
这基本上取决于您是否希望同步或异步更新控件。这完全取决于您的具体情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接