2010-01-19 50 views
6

我有一个我正在研究的具有多个工作线程的宠物项目。将所有内容输出到控制台越来越难,因此我想开发一个UI,每个线程都有一个输出区域。我想知道线程向UI发送更新的最佳方式。我有两个想法:从多个工作线程更新UI(.NET)

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

2)创建每个线程的回调函数,以便在新数据可用时调用UI Update(...)方法。 (2)出于两个原因:我不喜欢“检查”每个线程的想法,并且因为这是我的第一个多线程应用程序,并且(2)似乎比它可能更简单。我想知道:

  • 在简单性和效率方面哪个选项更可取?
  • 你有任何提示(2)或类似的东西(即更多的事件驱动)?

回答

12

您可以轻松实现(2)通过创建BackgroundWorker的组件和做的工作在他们的DoWork的处理程序:

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

优秀的...谢谢!现在我只需要等待我的日常工作,我就可以去做一些真正的编程。 – iandisme 2010-01-19 21:48:50

+0

在Compact Framework(3.5)中执行此项工作。 – Royal 2014-07-13 14:27:55

1

我为#2投票,但与BackgroundWorkers而不是System.Threading.Threads。

+0

而回调函数必须是线程安全的... – Oded 2010-01-19 21:39:58

0

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

This文章给出了一个快速概述。

0

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

查看here了解如何使用背景工作的示例。

1

在大多数情况下,最简单的事情就是使用Backgroundower组件的建议,我会强烈建议尽可能使用这种方法。如果出于某种原因,您无法为您的目的使用BackgroundWorker组件,例如,如果您使用.Net 1.1开发(yikes!)或者使用紧凑框架,则可能需要使用其他方法:

使用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"); 
    } 
} 
0

如果你创建你自己的线程(非的BackgroundWorker或线程池线程),你可以从你那是一个从工作线程称为主线程传递一个回调方法。这也可以让你传递参数给回调函数,甚至可以返回一个值(比如一个去/不去标志)。在回调您完成目标控制的调度更新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); 
    } 
}