Task.Run 和 UI 进度更新

Task.Run and UI Progress Updates

此代码片段来自 Stephen Cleary's blog,并给出了使用 Task.Run 时如何报告进度的示例。我想知道为什么更新 UI 没有跨线程问题,我的意思是为什么不需要调用?

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

Progress<T> 在实例化时捕获当前的 SynchronisationContext。每当您调用 Report 时,它都会秘密地将其委托给捕获的上下文。在示例中,捕获的上下文是 UI,这意味着没有发生异常。

Progress<T> 构造函数捕获当前 SynchronizationContext 对象。

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

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

换句话说,它之所以有效,是因为 Progress 旨在处理该问题,而无需开发人员明确说明。

你似乎很困惑,因为这个跨线程机制的一部分对开发人员来说是隐藏的,所以你只需要“接受并使用”:https://devblogs.microsoft.com/dotnet/async-in-4-5-enabling-progress-and-cancellation-in-async-apis

We introduced the IProgress interface to enable you to create an experience for displaying progress. This interface exposes a Report(T) method, which the async task calls to report progress. You expose this interface in the signature of the async method, and the caller must provide an object that implements this interface. Together, the task and the caller create a very useful linkage (and could be running on different threads).

We also provided the Progress class, which is an implementation of IProgress. You are encouraged to use Progress in your implementation, because it handles all the bookkeeping around saving and restoring the synchronization context. Progress exposes both an event and an Action callback, which are called when the task reports progress. This pattern enables you to write code that simply reacts to progress changes as they occur. Together, IProgress and Progress provide an easy way to pass progress information from a background task to the UI thread.

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