2014-07-02 21 views
1

所以我决定重写在WPF我的邮件客户端,因为我认为它的时候我从Windows窗体移动(我还是很喜欢它),但我面对有点问题。更新UI而Task.Run或TaskFactory.StartNew仍在工作

我使用BackgroundWorker在我的Windows窗体应用程序做东西,并在一个foreach我worker.ReportProgress(currentProgress);,这使我可以更新用户界面,因为事情正在做的很好的背景。

不过才刚刚开始一个新的WPF项目后,我注意到有一个工具箱没有BackgroundWorker(对于WPF应用程序),所以我去网上搜索,发现有些人在使用BackgroundWorker与WPF更新UI问题。因此,这让我觉得,在一个WPF应用程序使用BackgroundWorker有点哈克 - 我不希望出现这种情况。

在同一页上,另一位用户将他们引用到this页面,告诉他们在WPF中使用Task.Run而不是BackgroundWorker。在查看Task.Run文档后,我立即看到它如何有用,但我确实有一个问题。我没有看到“报告进度”的方式,或者随着事情的完成而更新UI。我看到的只是如何运行任务和“await”它;让我只有一个选择 - 在长时间运行的任务完成后更新UI。

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

+0

“BackgroundWorker”类是在WPF中的后台线程上更新UI的主要方法之一。你看过哪些文章表明这​​很黑客? –

+0

谢谢@AndrewStephens--我认为这很荒谬的原因是因为1)默认情况下它不在WPF应用程序的工具箱中,2)您必须手动添加worker.SupportsProgress = true; (由于缺少此控件的属性窗格,3)我可以看到其他人已经表示BackgroundWOrker无法使用委托(在WinForms中不需要)从WorkerCompleted或ProgressChanged事件内部更新UI - 这导致我相信也许BGWorker不完全支持或不打算在WPF应用程序中使用。 –

+0

这是一个不是UI控件的类,所以它不在工具箱中。您通常会在您的代码隐藏内部创建它的实例,或者如果使用MVVM,您将创建它的视图模型。 ProgressChanged事件在UI线程中引发,因此不需要委托。我确信WorkerCompleted也是如此,但是如果不是在“Dispatcher.Invoke()'调用中包装UI更新代码。再次,您会发现这是WPF中的常用技术,尤其是在从其他线程更新UI时。 –

回答

6

您可以BackroundWorker坚持,如果你选择。虽然这是非常古老的学校,但它并没有真正的黑客。正如有人说,如果你不能在你的工具箱中找到它,你可以随时申报,并直接从您的代码初始化它(不要忘了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的进展是,当你处理非常大量物品,以及每个项目的处理需要很短的时间(我们每秒钟讲10000个项目作为粗略指南)。在这种情况下,创建大量非常细粒度的和将会引入大量开销的场景。如果这是您的情况,您可以回到.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; 
     } 
    } 
}