将对象从后台工作线程传递到主线程

4

我有一个WPF应用程序,该程序执行外部程序以处理媒体文件。为了防止GUI在处理媒体文件时冻结,我通过backgroundworker将进程执行在单独的线程上。

        private void BackgroundWorkerExecProcess(Process process)
    {
        BackgroundWorker worker = new BackgroundWorker();
        worker.WorkerReportsProgress = false;
        worker.DoWork += DoWork;
        worker.RunWorkerCompleted += WorkerCompleted;
        worker.RunWorkerAsync(process);
    }
    void DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        Process process = e.Argument as Process;
        process.Start();
        string stderr = process.StandardError.ReadToEnd();
        //I want to display stderr on main thread
        process.WaitForExit();
    }
    void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        //some code to update gui telling user that process has finished
    }

所以,如果有东西被打印到stderr,我可以在调试器中看到它,但是如果我尝试对stderr进行任何操作,比如如果我有一个名为“_tbLog”的文本框并执行以下操作:
_tbLog.Text+=stderr;    

我从编译器那里得到了一个关于它们在不同线程上的错误。有没有一种方法可以将对象从工作线程传递到主线程?

4个回答

8

在DoWork方法中,将e.Result设置为您的对象。在WorkerCompleted方法中,您可以重新获取该对象…它将再次成为类型为object的e.Result。只需将其强制转换为原始的对象即可。WorkerCompleted应该在正确的线程上运行。

这是我的其中一个:

private void workerUpdateBuildHistory_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    UpdateStatusModel model = (UpdateStatusModel)e.Argument;
    BuildService buildService = new BuildService(model.TFSUrl);
    e.Result = buildService.UpdateBuildHistoryList(model);
}

private void workerUpdateBuildHistory_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    BuildHistoryListModel model = (BuildHistoryListModel)e.Result;
    if (model != null)
    {
        listViewPastBuilds.Items.Clear();
        foreach (var item in model.Builds)
        {
            listViewPastBuilds.Items.Add(item);
        }
    }
}

谢谢!那个很好用。我只是添加了 e.Result = stderr; 然后在 workercompleted 中,我能够使用 _tbLog.Text+=e.Result 作为字符串; - Jeff Crowell
1
请注意,读取e.Result将显示在DoWork中未处理的任何异常。完成事件应遵循一定的模式,请参见http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.runworkercompleted.aspx。 - H H

3

使用WorkerCompleted事件处理程序来更改UI,它在正确的线程上运行。你所要做的就是将字符串传递给事件处理程序。这就是DoWorkEventArgs.Result的设计目的。你将在事件处理程序中从e.Result中检索它。因此:

   void DoWork(object sender, DoWorkEventArgs e)
   {
      //...
      e.Result = stderr;
   }

   void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   {
      if (e.Error != null) DisplayError(e.Error);
      else _tbLog.Text += (string)e.Result;
   }

2

首先,您需要将任何结果对象(在本例中为字符串列表)放置在DoWorkEventArgs.Result属性中,然后通过RunWorkerCompletedArgs.Result属性检索此对象。

然后,连接Background worker的RunWorkedCompleted事件的事件处理程序,并使其通过RunWorkerCompletedEventArgs.Result属性返回所需的任何对象。

示例:

void DoWork(object sender, DoWorkEventArgs arg)
{
   List<string> results = new List<string>();
   results.Add("one");
   results.Add("two");
   results.Add("three");
   arg.Results = results;
}

void WorkComplete(object sender, runWorkerCompelteEventArgs arg)
{
   //Get our result back as a list of strings
   List<string> results = (List<string>)arg.Result;
   PrintResults(results);
}

注意:我没有测试过这段代码,但我相信它应该能编译通过。 http://msdn.microsoft.com/en-us/library/system.componentmodel.runworkercompletedeventargs.result.aspx http://msdn.microsoft.com/en-us/library/system.componentmodel.doworkeventargs.aspx

0

你也可以像 @Zembi 提到的那样使用 dispatcher:

  this.Dispatcher.Invoke( new Action( () => {
    _tbLog.Text+=stderr; 
  } ) );

你也可以使用TPL来确保事情在正确的线程上运行

-编辑-

这里有一篇好文章介绍不同的UI更新方式,包括使用TPL


我已经按照CrazyDart的解决方案使其工作,但如果我使用TPL,并在正确的线程(主线程)上运行进程,那么这是否也会防止GUI重新绘制,从而锁定应用程序? - Jeff Crowell
如果您在GUI线程上运行整个过程,是的。然而,TPL的一个关键优点是使用连续性(ContinueWith(..)方法)和调度程序轻松地在线程之间传递工作。您可以在线程池上启动一个新任务,当它完成时,您将在UI线程上安排一个更新UI的新任务。 - aL3891

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