BeginInvoke会阻塞用户界面,而Invoke不会。为什么?

6
我对遇到的跨线程访问场景感到困惑。这是我尝试做的事情:
主UI线程 - 菜单项点击时,我创建了一个后台工作线程并异步运行它。
private void actionSubMenuItem_Click(object sender, EventArgs e)
{
       ToolStripMenuItem itemSelected = (ToolStripMenuItem)sender;
       ExecuteTheActionSelected(itemSelected.Text);
}

方法ExecuteTheActionSelected如下所示:
private void ExecuteTheActionSelected(string actionSelected)
{
      BackgroundWorker localBackgroundWorker = new BackgroundWorker();
      localBackgroundWorker.DoWork += new DoWorkEventHandler(localBackgroundWorker_DoWork);
      localBackgroundWorker.RunWorkerAsync(SynchronizationContext.Current);
}

localBackgroundWorker_DoWork 方法包含以下内容:

 ActionExecutionHelper actionExecutioner = new ActionExecutionHelper()
 actionExecutioner.Execute();

那个类中的Execute方法具有方法调用程序,实际上在UI线程中调用事件处理程序:
 public void Execute()
 {
      // ---- CODE -----
      new MethodInvoker(ReadStdOut).BeginInvoke(null, null);
 }

 protected virtual void ReadStdOut()
 {
      string str;
      while ((str = executionProcess.StandardOutput.ReadLine()) != null)
      {
          object sender = new object();
          DataReceivedEventArgs e = new DataReceivedEventArgs(str);
          outputDataReceived.Invoke(sender, e); 
          //This delegate invokes UI event handler
      }
 }

UI事件处理程序如下所示:
private void executionProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    if (_dwExecuteAction != null)
    {
        _dwExecuteAction.ShowDataInExecutionWindow(e.Text);
    }
}

现在我们来谈谈跨线程问题:

public void ShowDataInExecutionWindow(string message)
{
     if (rchtxtExecutionResults.InvokeRequired)
     {
            rchtxtExecutionResults.Invoke(new ShowDataExecutionDelegate(ShowDataInExecutionWindow), message);
     }
     else
     {
            this.rchtxtExecutionResults.AppendText(message + Environment.NewLine);
     }
}

在这里,Invoke不会阻塞UI,而BeginInvoke会阻塞。 请帮助我理解这种情况,因为我很困惑。


很抱歉,我不知道答案,但我会采取的方法是查看多个调用级别和后台工作程序之间的交互方式,以及所有内容实际执行的位置,以便您了解所有内容如何配合。 - Matthew Walton
1个回答

7

是的,这是正常现象。使用Invoke()的好处在于它会阻塞工作线程。当您使用BeginInvoke()时,线程保持运转并以高于UI线程处理速度的速率发出调用请求。具体取决于您要求UI线程执行的任务,但在每秒1000次调用左右开始成为问题。

在这种情况下,UI线程停止响应,它不断地寻找另一个调用请求,同时不再执行其正常职责。输入和绘图请求不再被处理。

问题的明显原因是从进程检索到的每一行输出都有调用请求。这些请求过于频繁。您需要通过降低调用速率来解决此问题。有一个简单的规则,您只需要尝试让人类保持忙碌状态,每秒超过25次的调用将使您所产生的任何内容变得模糊。因此,缓冲行并测量自上次调用以来经过的时间。

还要注意,使用Invoke()是一个简单的解决方法,但不能完全保证有效。这是一个竞争,工作线程可能始终比主线程重新进入消息循环并读取下一条消息早一点。如果是这种情况,您仍然会遇到完全相同的问题。


好的,这解释了BeginInvoke()阻塞UI的原因。但是为什么Invoke()不会阻塞UI线程呢?当我使用Invoke()时,我可以轻松访问其他控件。 - Nagaraj Tantri
因为它会阻塞工作线程,从而使其无法频繁地向UI线程发送调用请求。关键点在于UI线程实际上并没有被阻塞,只是没有处理其所有正常职责。 - Hans Passant
还有一个问题!!你怎么会知道这些细节? :) 哥们,这需要更深入的知识,需要你探索更多。我搜索了很多网站,但从未得出这样的结论! - Nagaraj Tantri

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