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

时间:2010-01-19 21:35:00

标签: c# .net multithreading user-interface

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

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

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

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

  • 在简单性和效率方面哪个选项更可取?
  • 您是否有实施(2)或类似内容的提示(即更多事件驱动)?

6 个答案:

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