我有一个我正在研究的宠物项目,它有多个工作线程。将所有内容输出到控制台变得越来越难以遵循,所以我想开发一个每个线程有一个输出区域的UI。我想知道线程向UI发送更新的最佳方式。我有两个想法:
1)让每个线程在新数据可用时设置“DataUpdated”标志,并让UI定期检查新数据。
2)创建每个线程,回调一个UI Update(...)方法,以便在新数据可用时调用。
我目前倾向于(2)有两个原因:我不喜欢“检查”每个线程的想法,因为这是我的第一个多线程应用程序,(2)似乎比它可能更简单。我想知道:
答案 0 :(得分:12)
您可以通过创建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的优点在于它为您处理了许多混乱的交叉线程细节。它也符合事件驱动的更新模型,您(正确地)更喜欢显式回调。
答案 1 :(得分:1)
我也投票支持#2,但使用BackgroundWorkers代替System.Threading.Threads。
答案 2 :(得分:1)
在大多数情况下,最简单的方法是按照itowlson的回答中的建议使用BackgroundWorker组件,如果可能的话,我强烈建议使用该方法。如果出于某种原因,您不能将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");
}
}
答案 3 :(得分:0)
您可以让工作线程引发事件并让主UI线程添加事件处理程序。你需要小心,你不会引发太多的事件,因为如果你的工作线程每秒都会引发多个事件,它可能会变得很难看。
This文章提供了快速概述。
答案 4 :(得分:0)
在您的应用程序中实现多线程的首选方法是使用BackgroundWorker组件。 BackgroundWorker组件使用事件驱动的模型进行多线程处理。工作线程运行您的DoWork事件处理程序,创建控件的线程运行ProgressChanged和RunWorkerCompleted事件处理程序。
当您在ProgressChanged事件处理程序中更新UI控件时,它们会在主线程上自动更新,这将阻止您获得交叉线程异常。
查看here以获取有关如何使用backgroundworker的示例。
答案 5 :(得分:0)
如果您正在创建自己的线程(非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);
}
}