嵌套的后台工作者,从主线程更新UI

时间:2017-07-10 14:54:46

标签: c# multithreading asynchronous backgroundworker

我有两种方法需要运行异步。为了Method B()运行,它会调用Method A(),但有时Method A()会在其他地方调用。在Background Worker completed事件中,我更新了UI,如果从主线程调用Method A(),一切都很好。但是,当Method A()内部调用Method B()时,RunWorkerCompleted的{​​{1}}现在发生在Method A()的主题而不是主线程上。我能想到的唯一解决方案是在主线程上实现事件监听器,然后在Method B()的{​​{1}}方法中,引发此事件。任何人都可以告诉我如何实现这一目标,或者是否有更好的方法来处理这个问题?

2 个答案:

答案 0 :(得分:0)

BGW已经过时了。 其所有功能已被async/await,Task.Run,​​IProgress界面和CancellationToken/CancellationTokenSource取代。 .NET博客帖子Async in 4.5: Enabling Progress and Cancellation in Async APIs显示了在托管线程和异步API中如何进行和取消。

鉴于最早支持的.NET版本是4.5.2,您可以确信async/await始终可用。

BGW过时的原因之一是你无法嵌套或组合BGW。 BGW是在第一个.NET版本中引入的,用于以形式的背景执行简单的工作/这就是为什么它是System.ComponentModel的一部分并引发接口。

创建一个执行某些繁重处理的方法异步调用REST API对async/await来说是微不足道的:

HttpClient _restClient=new HttpClient();

async Task MethodA(int someParam)
{
    toolStripLabel.Text ="Starting ...";
    var result=await Task.Run(()=>FunctionB(someParam));
    toolStripLabel.Text ="Calling ...";
    var response=await _restClient.GetAsync(${baseUrl}/{result}");
    toolStripLabel.Text ="Finished";        
    this.TextBox1.Text=response;
}

Task.Run将在后台线程中运行FunctionBawait释放当前的UI线程,直到Task.Run生成的任务完成。一旦执行,执行将返回到UI线程。用户界面可以直接修改。

调用HttpClient或任何其他异步API的异步方法在后台线程上运行 - 这样的IO操作在操作系统级别通过IO完成端口异步处理 - 大致是在调用时调用的回调IO操作完成。操作系统不会阻止等待磁盘或网络响应。

如果FunctionB运行太久以至于需要一些进度报告,该怎么办?

我们可以使用复杂的类来报告进度,或者只是将中间结果发送给正在收听的人:

public class Report
{ 
    public string Message{get;set;}
    public string Status{get;set;}
    public int Progress{get;set;}
    //Why not?
    public Color MessageColor{get;set;}

    Report(string message,string status, int progress, Color color)
    {
    ...
    }
}

在这种情况下,Progress<T>只会调用此方法来更新UI并记录任何消息:

void ReportProgress(Report report)
{
    //Doesn't have to be UI only
    Log.Verbose(report.Message);
    toolStripLabel.Text =report.Status;
    toolStripLabel.BackColor=report.Color;
    toolStripProgress.Value=report.Progress;
}

MethodA本身可以使用Progress< T>将自己与用户界面分开:

async Task MethodA(int someParam)
{
    IProgress<Report> progress=new Progress<Report>(ReportProgress)
    progress.Report(new Report("Starting","Starting",0,Color.White);

    var result=await Task.Run(()=>FunctionB(someParam,progress));

    progress.Report(new Report("Calling API","Calling",95,Color.Aqua);

    var response=await _restClient.GetAsync(${baseUrl}/{result}");

    progress.Report(new Report("Finished progessing","Finished",100,Color.White);

    this.TextBox1.Text=response;
}

FunctionB可以报告这样的进展:

int FunctionB(int someParam,IProgress<Report> progress)
{
    for (int i=0;i<10000000; i++)
    {
        //Do the work and report
       if (i % 100000 ==0)
       {
           var value=(int)(i / 100000.0);
           progress.Report(new Report("Blah blah","Processing",value,Color.Pink);
       }
       progress.Report(new Report("Blah blah","Processing Finished",100,Color.Red);

    }
}

答案 1 :(得分:-1)

您还可以调用需要更新的UI元素。这看起来像这样:

textBox1.Invoke((MethodInvoker) delegate { textBox1.Text = "TEST"; });