C# 正确使用 Task.Run 和 async-await 时
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18013523/
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
When correctly use Task.Run and when just async-await
提问by Lukas K
I would like to ask you on your opinion about the correct architecture when to use Task.Run
. I am experiencing laggy UI in our WPF .NET 4.5
application (with Caliburn Micro framework).
我想请教一下您对何时使用Task.Run
. 我在我们的 WPF .NET 4.5 应用程序(使用 Caliburn Micro 框架)中遇到了滞后的 UI。
Basically I am doing (very simplified code snippets):
基本上我在做(非常简化的代码片段):
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// Makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
await DoCpuBoundWorkAsync();
await DoIoBoundWorkAsync();
await DoCpuBoundWorkAsync();
// I am not really sure what all I can consider as CPU bound as slowing down the UI
await DoSomeOtherWorkAsync();
}
}
From the articles/videos I read/saw, I know that await
async
is not necessarily running on a background thread and to start work in the background you need to wrap it with await Task.Run(async () => ... )
. Using async
await
does not block the UI, but still it is running on the UI thread, so it is making it laggy.
从我阅读/看到的文章/视频中,我知道它await
async
不一定在后台线程上运行,并且要在后台开始工作,您需要用 await 包装它Task.Run(async () => ... )
。使用async
await
不会阻塞 UI,但它仍然在 UI 线程上运行,所以它使它变得滞后。
Where is the best place to put Task.Run?
在哪里放置 Task.Run 的最佳位置?
Should I just
我应该只是
Wrap the outer call because this is less threading work for .NET
, or should I wrap only CPU-bound methods internally running with
Task.Run
as this makes it reusable for other places? I am not sure here if starting work on background threads deep in core is a good idea.
包装外部调用,因为这对于 .NET 来说是更少的线程工作
,或者我应该只包装内部运行的 CPU 绑定方法,
Task.Run
因为这使其可在其他地方重用?我不确定在这里开始在核心深处的后台线程上工作是否是个好主意。
Ad (1), the first solution would be like this:
广告(1),第一个解决方案是这样的:
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
HideLoadingAnimation();
}
// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.
Ad (2), the second solution would be like this:
广告(2),第二种解决方案是这样的:
public async Task DoCpuBoundWorkAsync()
{
await Task.Run(() => {
// Do lot of work here
});
}
public async Task DoSomeOtherWorkAsync(
{
// I am not sure how to handle this methods -
// probably need to test one by one, if it is slowing down UI
}
采纳答案by Stephen Cleary
Note the guidelines for performing work on a UI thread, collected on my blog:
请注意在我的博客上收集的在UI 线程上执行工作的指南:
- Don't block the UI thread for more than 50ms at a time.
- You can schedule ~100 continuations on the UI thread per second; 1000 is too much.
- 一次不要阻塞 UI 线程超过 50 毫秒。
- 您可以每秒在 UI 线程上安排约 100 个延续;1000太多了。
There are two techniques you should use:
您应该使用两种技术:
1) Use ConfigureAwait(false)
when you can.
1) 尽可能使用ConfigureAwait(false)
。
E.g., await MyAsync().ConfigureAwait(false);
instead of await MyAsync();
.
例如,await MyAsync().ConfigureAwait(false);
代替await MyAsync();
.
ConfigureAwait(false)
tells the await
that you do not need to resume on the current context (in this case, "on the current context" means "on the UI thread"). However, for the rest of that async
method (after the ConfigureAwait
), you cannot do anything that assumes you're in the current context (e.g., update UI elements).
ConfigureAwait(false)
告诉await
您不需要在当前上下文上恢复(在这种情况下,“在当前上下文上”意味着“在 UI 线程上”)。但是,对于该async
方法的其余部分(在 之后ConfigureAwait
),您不能做任何假设您处于当前上下文中的事情(例如,更新 UI 元素)。
For more information, see my MSDN article Best Practices in Asynchronous Programming.
有关更多信息,请参阅我的 MSDN 文章异步编程中的最佳实践。
2) Use Task.Run
to call CPU-bound methods.
2)Task.Run
用于调用受 CPU 限制的方法。
You should use Task.Run
, but not within any code you want to be reusable (i.e., library code). So you use Task.Run
to callthe method, not as part of the implementationof the method.
您应该使用Task.Run
, 但不要在您想要重用的任何代码(即库代码)中使用。所以你Task.Run
用来调用方法,而不是作为方法实现的一部分。
So purely CPU-bound work would look like this:
所以纯粹受 CPU 限制的工作看起来像这样:
// Documentation: This method is CPU-bound.
void DoWork();
Which you would call using Task.Run
:
您将使用Task.Run
以下方法调用:
await Task.Run(() => DoWork());
Methods that are a mixtureof CPU-bound and I/O-bound should have an Async
signature with documentation pointing out their CPU-bound nature:
方法是一个混合的CPU绑定和I / O密集型应该有一个Async
与文件指出他们的CPU绑定性质签名:
// Documentation: This method is CPU-bound.
Task DoWorkAsync();
Which you would also call using Task.Run
(since it is partially CPU-bound):
您也可以使用Task.Run
它来调用(因为它部分受 CPU 限制):
await Task.Run(() => DoWorkAsync());
回答by Paul Hatcher
One issue with your ContentLoader is that internally it operates sequentially. A better pattern is to parallelize the work and then sychronize at the end, so we get
您的 ContentLoader 的一个问题是它在内部按顺序运行。更好的模式是并行化工作,然后在最后同步,所以我们得到
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
var tasks = new List<Task>();
tasks.Add(DoCpuBoundWorkAsync());
tasks.Add(DoIoBoundWorkAsync());
tasks.Add(DoCpuBoundWorkAsync());
tasks.Add(DoSomeOtherWorkAsync());
await Task.WhenAll(tasks).ConfigureAwait(false);
}
}
Obviously, this doesn't work if any of the tasks require data from other earlier tasks, but should give you better overall throughput for most scenarios.
显然,如果任何任务需要来自其他早期任务的数据,这将不起作用,但在大多数情况下应该为您提供更好的整体吞吐量。