【问题标题】:Update UI from multiple worker threads (.NET)从多个工作线程 (.NET) 更新 UI
【发布时间】:2011-01-07 00:35:56
【问题描述】:

我正在处理一个具有多个工作线程的宠物项目。将所有内容输出到控制台变得越来越难以理解,所以我想开发一个每个线程都有一个输出区域的 UI。我想知道线程向 UI 发送更新的最佳方式。我有两个想法:

1) 当有新数据可用时,让每个线程设置一个“DataUpdated”标志,并让 UI 定期检查新数据。

2) 创建每个线程,并在新数据可用时调用 UI Update(...) 方法。

我目前倾向于 (2) 有两个原因:我不喜欢“检查”每个线程的想法,因为这是我的第一个多线程应用程序,并且 (2) 看起来比它可能的简单。我想知道:

  • 就简单性和效率而言,哪个选项更可取?
  • 对于实施 (2) 或类似的方法(即更多事件驱动),您有什么建议吗?

【问题讨论】:

    标签: c# .net multithreading user-interface


    【解决方案1】:

    您可以通过创建 BackgroundWorker 组件并在其 DoWork 处理程序中完成工作来轻松实现 (2):

    BackgroundWorker bw = new BackgroundWorker();
    bw.WorkerReportsProgress = true;
    bw.DoWork += /* your background work here */;
    bw.ProgressChanged += /* your UI update method here */;
    bw.RunWorkerAsync();
    

    每个 BackgroundWorker 都可以通过调用 ReportProgress 向 UI 线程报告进度:虽然这主要是为报告有界进程的进度而设计的,但这不是强制性的——如果这是您的 UI 更新需要的,您也可以传递您自己的自定义数据.您将从 DoWork 处理程序调用 ReportProgress。

    BackgroundWorker 的好处在于它为您处理了许多杂乱的跨线程细节。它还符合事件驱动的更新模型,您(正确地)更喜欢显式回调。

    【讨论】:

    • 太好了……谢谢!现在我只需要等待离开我的日常工作,这样我就可以去做一些真正的编程了。
    【解决方案2】:

    我也投票给 #2,但使用 BackgroundWorkers 而不是 System.Threading.Threads。

    【讨论】:

      【解决方案3】:

      在大多数情况下,最简单的做法是按照 itowlson 的回答中的建议使用 BackgroundWorker 组件,如果可能的话,我强烈建议使用这种方法。如果由于某种原因,您不能将 BackgroundWorker 组件用于您的目的,例如如果您正在使用 .Net 1.1(哎呀!)或紧凑型框架进行开发,那么您可能需要使用另一种方法:

      使用 Winform 控件,您必须避免修改任何线程上的控件,而不是最初创建控件的线程。 BackgroundWorker 组件会为您处理这个,但如果您不使用它,那么您可以并且应该使用 System.Windows.Forms.Control 类中的 InvokeRequired 属性和 Invoke 方法。下面是一个使用这个属性和方法的例子:

      public partial class MultithreadingForm : Form
      {
          public MultithreadingForm()
          {
              InitializeComponent();
          }
      
          // a simple button event handler that starts a worker thread
          private void btnDoWork_Click(object sender, EventArgs e)
          {
              Thread t = new Thread(WorkerMethod);
              t.Start();
          }
      
          private void ReportProgress(string message)
          {
              // check whether or not the current thread is the main UI thread
              // if not, InvokeRequired will be true
              if (this.InvokeRequired)
              {
                  // create a delegate pointing back to this same function
                  // the Invoke method will cause the delegate to be invoked on the main UI thread
                  this.Invoke(new Action<string>(ReportProgress), message);
              }
              else
              {
                  // txtOutput is a UI control, therefore it must be updated by the main UI thread
                  if (string.IsNullOrEmpty(this.txtOutput.Text))
                      this.txtOutput.Text = message;
                  else
                      this.txtOutput.Text += "\r\n" + message;
              }
          }
      
          // a generic method that does work and reports progress
          private void WorkerMethod()
          {
              // step 1
              // ...
              ReportProgress("Step 1 completed");
      
              // step 2
              // ...
              ReportProgress("Step 2 completed");
      
              // step 3
              // ...
              ReportProgress("Step 3 completed");
          }
      }
      

      【讨论】:

        【解决方案4】:

        您可以让工作线程引发事件并让主 UI 线程添加事件处理程序。您需要注意不要引发太多事件,因为如果您的工作线程每秒引发多个事件,它可能会变得很难看。

        This 文章提供了一个快速概览。

        【讨论】:

          【解决方案5】:

          在您的应用程序中实现多线程的首选方法是使用BackgroundWorker 组件。 BackgroundWorker 组件使用事件驱动模型进行多线程处理。工作线程运行您的 DoWork 事件处理程序,创建控件的线程运行您的 ProgressChanged 和 RunWorkerCompleted 事件处理程序。
          当您在 ProgressChanged 事件处理程序中更新您的 UI 控件时,它们会在主线程上自动更新,这将防止您获得跨线程异常。

          查看here 以获取有关如何使用后台工作程序的示例。

          【讨论】:

            【解决方案6】:

            如果您正在创建自己的线程(非 BackgroundWorker 或 ThreadPool 线程),您可以从工作线程调用的主线程传递回调方法。这还允许您将参数传递给回调,甚至返回一个值(例如 go/no-go 标志)。在您的回调中,您通过目标控件的 Dispatcher 更新 UI:

            public void UpdateUI(object arg)
            {
                controlToUpdate.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.Normal
                    , new System.Windows.Threading.DispatcherOperationCallback(delegate
                    {
                        controToUpdate.property = arg;
                        return null;
                    }), null);
                }
            } 
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2023-04-01
              • 1970-01-01
              • 1970-01-01
              • 2017-12-14
              • 1970-01-01
              • 2012-12-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多