在Task.Run或TaskFactory.StartNew仍在工作时更新UI

jay*_*t55 1 .net c# wpf task-parallel-library

因此我决定在WPF中重写我的邮件客户端,因为我认为这是我从Windows Forms(我仍然喜欢)的时间,但我面临一些问题.

我BackgroundWorker在我的Windows窗体应用程序中使用了一个东西,并在foreach中worker.ReportProgress(currentProgress);,这使我可以更新UI,因为事情在后台完成,这很棒.

但是刚刚开始一个新的WPF项目后,我注意到BackgroundWorker工具箱中没有(对于WPF应用程序)所以我去网上搜索,发现有些人在使用BackgroundWorkerWPF时更新UI有问题.所以这让我觉得BackgroundWorker在WPF应用程序中使用有点hacky - 而且我不希望这样.

在同一页面上,另一个用户将他们引用到此页面,告诉他们使用Task.Run而不是BackgroundWorker在WPF中.在查看Task.Run文档后,我立即看到它是如何有用的,但我确实有一个问题.我没有看到"报告进度"的方法或者在完成任务时更新UI.我只看到如何运行任务和" await"它; 只留下一个选项 - 在长时间运行的任务完成后更新UI.

当Task.Run/ TaskFactory.StartNew仍在工作时,我们如何更新WPF桌面应用程序的UI ?

Kir*_*kiy 6

BackroundWorker如果你愿意,你可以坚持下去.虽然这是非常老派,但没有什么真正的hacky.正如其他人所说,如果你在工具箱中找不到它,你可以直接从代码中声明并初始化它(不要忘记using System.ComponentModel;指令).

Stephen Cleary在BackgroundWorkervs 上有一系列精彩的博客文章Task,强调了每种方法的差异和局限性.如果你在围栏或只是好奇,这绝对值得一读.

http://blog.stephencleary.com/2013/05/taskrun-vs-backgroundworker-intro.html

如果您决定沿着Task+ async/await路线走下去,那么您应该记住一些与进度报告特别相关的事情.

一般来说,您的目标应该是await Task.Run将最小的有意义的工作量封装起来.然后,您的async方法的其余部分将在调度程序上执行SynchronizationContext(假设它是在调度程序线程上启动的)并且将能够直接更新UI,如下所示:

List<object> items = GetItemsToProcess();
int doneSoFar = 0;

foreach (var item in items)
{
    await Task.Run(() => SomeCpuIntensiveWorkAsync(item));

    doneSoFar++;

    int progressPercentage = (int)((double)doneSoFar / items.Count * 100);

    // Update the UI.
    this.ProgressBar.Value = progressPercentage;
}
Run Code Online (Sandbox Code Playgroud)

这是在async世界范围内实施进度报告的最简单方法.

唯一一次我能想象报告从您传递给代表体内的进展Task.Run是,当你正在处理一个非常大的数量的项目,每个项目的处理需要很短的时间(我们谈论万件每秒作为粗略指南).在这种情况下,创建大量非常细粒度的Tasks并且await它们将引入显着的开销.如果是这种情况,您可以回退到.NET 4中引入的进度报告机制:Progress<T>/ IProgress<T>.它与BackgroundWorker报告的进展方式非常相似(因为它依赖于事件),并且在决定何时回发到调度程序上下文方面提供了更多的灵活性.

public async Task DoWorkAsync()
{
    // Let's assume we're on the UI thread now.
    // Dummy up some items to process.
    List<object> items = GetItemsToProcess();

    // Wire up progress reporting.
    // Creating a new instance of Progress
    // will capture the SynchronizationContext
    // any any calls to IProgress.Report
    // will be posted to that context.
    Progress<int> progress = new Progress<int>();

    progress.ProgressChanged += (sender, progressPercentage) =>
    {
        // This callback will run on the thread which
        // created the Progress<int> instance.
        // You can update your UI here.
        this.ProgressBar.Value = progressPercentage;
    };

    await Task.Run(() => this.LongRunningCpuBoundOperation(items, progress));
}

private void LongRunningCpuBoundOperation(List<object> items, IProgress<int> progress)
{
    int doneSoFar = 0;
    int lastReportedProgress = -1;

    foreach (var item in items)
    {
        // Process item.
        Thread.Sleep(1);

        // Calculate and report progress.
        doneSoFar++;

        var progressPercentage = (int)((double)doneSoFar / items.Count * 100);

        // Only post back to the dispatcher SynchronizationContext
        // if the progress percentage actually changed.
        if (progressPercentage != lastReportedProgress)
        {
            // Note that progress is IProgress<int>,
            // not Progress<int>. This is important
            // because Progress<int> implements
            // IProgress<int>.Report explicitly.
            progress.Report(progressPercentage);

            lastReportedProgress = progressPercentage;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)