wpf 在 Task.Run 或 TaskFactory.StartNew 仍在工作时更新 UI
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24529422/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Update UI while Task.Run or TaskFactory.StartNew is still working
提问by jay_t55
So I have decided to rewrite my Mail client in WPF as I think it's about time I move on from Windows Forms (I still like it), but I am facing a bit of a problem.
所以我决定在 WPF 中重写我的邮件客户端,因为我认为是时候从 Windows Forms 开始了(我仍然喜欢它),但是我面临着一些问题。
I use a BackgroundWorkerin my Windows Forms app to do stuff and in a foreach I worker.ReportProgress(currentProgress);and this allows me to update the UI as things are being done in the background which is great.
我BackgroundWorker在我的 Windows 窗体应用程序中使用 a来做一些事情,在 foreach I 中worker.ReportProgress(currentProgress);,这允许我在后台完成工作时更新 UI,这很棒。
But just now after starting a new WPF project, I notice that there is no BackgroundWorkerin the Toolbox (for WPF apps) so I go searching online, and found that some people have problems updating the UI while using BackgroundWorkerwith WPF. So this makes me think that using BackgroundWorkerin a WPF app is a bit hacky - and I don't want that.
但是刚刚开始一个新的WPF项目后,我注意到BackgroundWorker工具箱中没有(用于WPF应用程序)所以我去网上搜索,发现有些人在使用BackgroundWorkerWPF时更新UI有问题。所以这让我觉得BackgroundWorker在 WPF 应用程序中使用有点麻烦 - 我不想要那样。
On that same page, another user refers them to thispage, telling them to use Task.Runinstead of BackgroundWorkerin WPF. Upon looking at Task.Rundocs, I immediately see how it can be useful, however I do have one concern. I do not see a way to "Report Progress" or to update the UI as things are being done. All I see is how to Run a Task and "await" it; leaving me with just one option - update the UI after the long-running Task has completed.
在同一页面上,另一个用户将他们引用到此页面,告诉他们使用Task.Run而不是BackgroundWorker在 WPF 中。在查看Task.Run文档时,我立即看到它是如何有用的,但是我确实有一个问题。我没有看到“报告进度”或在完成工作时更新 UI 的方法。我所看到的只是如何运行任务和“ await”它;让我只有一个选择 - 在长时间运行的任务完成后更新 UI。
How can we update the UI of a WPF desktop app while Task.Run/TaskFactory.StartNewis still working?
我们如何在Task.Run/TaskFactory.StartNew仍在工作时更新 WPF 桌面应用程序的 UI ?
回答by Kirill Shlenskiy
You can stick with BackroundWorkerif you so choose. There is nothing really hacky about it although it is very old-school. As others said, if you can't find it in your toolbox, you can always declare and initialise it straight from your code (don't forget the using System.ComponentModel;directive).
BackroundWorker如果你愿意,你可以坚持下去。尽管它非常老派,但它并没有什么特别的地方。正如其他人所说,如果你在你的工具箱中找不到它,你总是可以直接从你的代码中声明和初始化它(不要忘记using System.ComponentModel;指令)。
Stephen Cleary has an excellent series of blog posts on BackgroundWorkervs Task, which highlights the differences and limitations of each approach. It's definitely worth a read if you're on the fence or just curious.
Stephen Cleary 在BackgroundWorkervs上发表了一系列出色的博客文章Task,重点介绍了每种方法的差异和局限性。如果您在围栏上或只是好奇,这绝对值得一读。
http://blog.stephencleary.com/2013/05/taskrun-vs-backgroundworker-intro.html
http://blog.stephencleary.com/2013/05/taskrun-vs-backgroundworker-intro.html
If you do decide to go down the Task+ async/awaitroute, there are a couple of things specifically related to progress reporting that you should keep in mind.
如果您确实决定采用Task+async/await路线,则应牢记与进度报告特别相关的几件事。
Generally you should be aiming to have your await Task.Runencapsulate the smallest meaningful amount of work possible. The rest of your asyncmethod will then execute on the dispatcher SynchronizationContext(assuming that it was started on the dispatcher thread) and will be able to update the UI directly, like so:
通常,您的目标应该是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;
}
This is the easiest way of implementing progress reporting in the asyncworld.
这是async世界上实施进度报告的最简单方法。
The only time I can imagine reporting the progress from within the body of the delegate you pass to Task.Runis when you're processing a verylarge number of items, and the processing of each item takes a very short time (we're talking 10,000 items per second as a rough guide). In such a scenario creating a large number of extremely fine-grained Tasks and awaiting them will introduce significant overhead. If this is your case you can fall back to the progress reporting mechanism introduced in .NET 4: Progress<T>/IProgress<T>. It's quite similar to the way the BackgroundWorkerreports progress (in that it relies on events) and it provides a bit more flexibility in terms of deciding when you get to post back to the dispatcher context.
唯一一次我能想象报告从您传递给代表体内的进步Task.Run,当你处理是非常大量的项目,每个项目的处理需要很短的时间(我们谈论万件每秒作为粗略指南)。在这种情况下,创建大量极细粒度的Tasks 并对其进行awaiting 将引入大量开销。如果是这种情况,您可以使用 .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;
}
}
}

