Task.Run和UI进度更新

此代码段来自Stephen Cleary的博客,并提供了使用Task.Run时如何报告进度的示例。 我想知道为什么没有更新UI的交叉线程问题,我的意思是为什么不需要调用?

private async void button2_Click(object sender, EventArgs e) { var progressHandler = new Progress(value => { label2.Text = value; }); var progress = progressHandler as IProgress; await Task.Run(() => { for (int i = 0; i != 100; ++i) { if (progress != null) progress.Report("Stage " + i); Thread.Sleep(100); } }); label2.Text = "Completed."; } 

Progress在实例化时捕获当前的SynchronisationContext 。 无论何时调用Report ,它都会秘密地将其委托给捕获的上下文。 在该示例中,捕获的上下文是UI,意味着不会发生exception。

Progress构造函数捕获当前的SynchronizationContext对象。

SynchronizationContext类是一个抽象所涉及的线程模型细节的工具。 也就是说,在Windows窗体中它将使用Control.Invoke ,在WPF中它将使用Dispatcher.Invoke等。

调用progress.Report对象时, Progress对象本身知道它应该使用捕获的SynchronizationContext运行其委托。

换句话说,它的工作原理是因为Progress目的是为了处理这种情况,而开发人员不必明确说出来。

看起来你很困惑,因为这个跨线程机器的一部分是隐藏在开发人员眼中的,所以你只需要“接受和使用”: http : //blogs.msdn.com/b/dotnet/archive/2012 /06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx

我们引入了IProgress界面,使您能够创建显示进度的体验。 此接口公开Report(T)方法,异步任务调用该方法来报告进度。 您在async方法的签名中公开此接口,并且调用方必须提供实现此接口的对象。 同时,任务和调用者创建了一个非常有用的链接(并且可以在不同的线程上运行)。

我们还提供了Progress类,它是IProgress的一个实现。 建议您在实现中使用Progress,因为它会处理有关保存和恢复同步上下文的所有簿记。 Progress公开事件和Action回调,在任务报告进度时调用它们。 通过此模式,您可以编写代码,以便在进度发生变化时立即做出反应。 IProgress和Progress共同提供了一种将进度信息从后台任务传递到UI线程的简便方法。

还有一件事需要提及: 完成部分工作 ,将调用进度通知, 而不仅仅是在那一刻 。 因此,如果您的UI线程处于空闲状态并且您有备用CPU核心,则延迟将几乎为零。 如果您的UI线程忙,则在UI线程恢复空闲之前不会调用通知(无论您的计算机有多少备用CPU核心)。