从多个工作线程更新UI (.NET)

6

我有一个宠物项目,涉及多个工作线程。将所有输出都放在控制台中变得难以跟踪,因此我想开发一个UI,每个线程都有一个输出区域。我想知道线程向UI发送更新的最佳方法。我有两个想法:

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

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

目前,我倾向于(2),原因有两个:我不喜欢“检查”每个线程的想法,因为这是我的第一个多线程应用程序,(2)似乎比实际更简单。我想知道:

  • 从简单性和效率方面来看,哪个选项更好?
  • 您对实现(2)或类似事件驱动的内容有什么建议吗?
6个回答

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的好处是它为您处理了许多混乱的跨线程细节。 它还符合您(正确地)更喜欢显式回调而不是事件驱动模型的更新。


2
太好了...谢谢!现在我只需要等待离开白天的工作,这样我就可以去做一些真正的编程了。 - iandisme

2
在大多数情况下,最简单的方法是使用BackgroundWorker组件,正如itowlson所建议的那样,如果可能的话,我强烈建议使用这种方法。如果由于某种原因,您无法将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");
    }
}

1

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);
    }
} 

0

你可以让你的工作线程引发事件,然后主UI线程添加事件处理程序。但要小心,不要引发过多事件,因为如果你的工作线程每秒引发多个事件,可能会变得混乱。

这篇文章提供了一个快速概述。


0

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

在此处查看如何使用backgroundworker的示例。


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