所以我决定在WPF中重写我的邮件客户端,因为我认为这是关于我从Windows Forms继续运行的时间(我仍然喜欢它),但我遇到了一些问题。
我在我的Windows窗体应用程序中使用BackgroundWorker
来执行操作,并在fore {I worker.ReportProgress(currentProgress);
中使用,这样我就可以在背景中完成更新UI,这很棒。< / p>
但是刚刚开始一个新的WPF项目之后,我注意到工具箱中没有BackgroundWorker
(对于WPF应用程序)所以我去网上搜索,发现有些人在使用时更新了UI WPF BackgroundWorker
。所以这让我觉得在WPF应用程序中使用BackgroundWorker
有点hacky - 我不希望这样。
在同一页面上,其他用户将其引荐至this页面,告诉他们在WPF中使用Task.Run
代替BackgroundWorker
。在查看Task.Run
文档后,我立即看到它是如何有用的,但我确实有一个问题。我没有看到“报告进度”的方法或者在完成任务时更新UI。我只看到如何运行任务和“await
”它;只留下一个选项 - 在长时间运行的任务完成后更新UI。
如果Task.Run
/ TaskFactory.StartNew
仍然有效,我们如何更新WPF桌面应用的用户界面?
答案 0 :(得分:6)
如果您愿意,可以坚持使用BackroundWorker
。虽然这是非常老派,但没有什么真正的hacky。正如其他人所说,如果你无法在工具箱中找到它,你可以直接从代码中声明并初始化它(不要忘记using System.ComponentModel;
指令)。
Stephen Cleary在BackgroundWorker
vs 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;
}
这是在async
世界中实施进度报告的最简单方法。
我唯一想象的是,当您处理非常大量项目时,会在您传递给Task.Run
的代理机构内部报告进度。每个项目的处理需要很短的时间(我们每秒说10,000个项目作为粗略的指导)。在这种情况下,创建大量极细粒度Task
和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;
}
}
}