【问题标题】:Update UI while Task.Run or TaskFactory.StartNew is still working在 Task.Run 或 TaskFactory.StartNew 仍在工作时更新 UI
【发布时间】:2015-06-28 07:41:36
【问题描述】:

所以我决定在 WPF 中重写我的邮件客户端,因为我认为是时候从 Windows 窗体(我仍然喜欢它)继续前进了,但我遇到了一些问题。

我在我的 Windows 窗体应用程序中使用 BackgroundWorker 来做事,并在 foreach 中使用 worker.ReportProgress(currentProgress);,这让我可以在后台执行操作时更新 UI,这很棒。

但是刚刚开始一个新的WPF项目,发现工具箱里没有BackgroundWorker(用于WPF应用)所以我去网上搜索,发现有些人在使用@987654325时更新UI有问题@ 使用 WPF。所以这让我觉得在 WPF 应用程序中使用 BackgroundWorker 有点笨拙——我不希望这样。

在同一页面上,另一个用户将他们引至 this 页面,告诉他们在 WPF 中使用 Task.Run 而不是 BackgroundWorker。在查看 Task.Run 文档后,我立即看到它是如何有用的,但我确实有一个担忧。我看不到“报告进度”或在完成工作时更新 UI 的方法。我所看到的只是如何运行任务和“await”它;只剩下一个选项 - 在长时间运行的任务完成后更新 UI。

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

【问题讨论】:

  • BackgroundWorker 类是在 WPF 的后台线程上更新 UI 的主要方法之一。你看过哪些文章表明它很hacky?
  • 谢谢@AndrewStephens - 我认为它是hacky的原因是因为1)默认情况下它不在WPF应用程序的工具箱中,2)你必须手动添加worker.SupportsProgress = true; (由于缺少此控件的属性窗格,3)我可以看到其他人说 BackgroundWOrker 无法在不使用委托的情况下从 WorkerCompleted 或 ProgressChanged 事件内部更新 UI(这在 WinForms 中不是必需的) - 这导致我相信 BGWorker 可能不完全受支持或不打算在 WPF 应用程序中使用。
  • 它是一个类而不是 UI 控件,所以它不会出现在工具箱中。如果使用 MVVM,您通常会在代码隐藏或视图模型中创建它的实例。在 UI 线程上引发 ProgressChanged 事件,因此不需要委托。我敢肯定 WorkerCompleted 也会这样做,但如果不只是将 UI 更新代码包装在 Dispatcher.Invoke() 调用中。您会再次发现这是 WPF 中的一种常用技术,尤其是在从其他线程更新 UI 时。
  • 非常感谢@Andrew!

标签: c# .net wpf task-parallel-library


【解决方案1】:

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

Stephen Cleary 在 BackgroundWorkerTask 上发表了一系列出色的博文,重点介绍了每种方法的差异和局限性。如果您犹豫不决或只是好奇,绝对值得一读。

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;
}

这是在async 世界中实现进度报告的最简单方法。

我能想象的唯一一次从您传递给 Task.Run 的委托正文中报告进度的情况是,您正在处理 非常 大量的项目,并且每个项目的处理item 需要很短的时间(我们说每秒 10,000 个项目作为粗略的指导)。在这种情况下,创建大量极细粒度的Tasks 和awaiting 它们将引入大量开销。如果是这种情况,您可以退回到 .NET 4 中引入的进度报告机制:Progress&lt;T&gt;/IProgress&lt;T&gt;。这与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;
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-03
    • 1970-01-01
    • 2020-07-13
    • 2020-07-02
    相关资源
    最近更新 更多