本文翻译自:When correctly use Task.Run and when just async-await
I would like to ask you on your opinion about the correct architecture when to use Task.Run
. 我想问您关于何时使用Task.Run
的正确体系结构的Task.Run
。 I am experiencing laggy UI in our WPF .NET 4.5 application (with Caliburn Micro framework). 我在我们的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 () => ... )
. 从我阅读/看到的文章/视频中,我知道await
async
不一定在后台线程上运行,要在后台启动工作,您需要使用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. 使用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 打包外部调用,因为这减少了.NET的线程工作
, or should I wrap only CPU-bound methods internally running with
Task.Run
as this makes it reusable for other places? ,还是只包装内部使用Task.Run
运行的受CPU约束的方法,因为这可以在其他地方重用? I am not sure here if starting work on background threads deep in core is a good idea. 我不确定在内核中深入后台线程是否是一个好主意。
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
}
#1楼
参考:https://stackoom.com/question/1dA8H/何时正确使用Task-Run以及何时仅异步等待
#2楼
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. 不要一次将UI线程阻塞50毫秒以上。
- You can schedule ~100 continuations on the UI thread per second; 您可以每秒在UI线程上安排约100个连续时间; 1000 is too much. 1000太多了。
There are two techniques you should use: 您应该使用两种技术:
1) Use ConfigureAwait(false)
when you can. 1)可以时使用ConfigureAwait(false)
。
Eg, await MyAsync().ConfigureAwait(false);
例如, await MyAsync().ConfigureAwait(false);
instead of await MyAsync();
而不是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"). ConfigureAwait(false)
告诉await
您不需要在当前上下文上继续(在这种情况下,“在当前上下文上”意味着“在UI线程上”)。 However, for the rest of that async
method (after the ConfigureAwait
), you cannot do anything that assumes you're in the current context (eg, update UI elements). 但是,对于该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 (ie, library code). 您应该使用Task.Run
,但不要在要重用的任何代码(即库代码)中使用。 So you use Task.Run
to call the method, not as part of the implementation of the method. 因此,您可以使用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 mixture of 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());
#3楼
One issue with your ContentLoader is that internally it operates sequentially. ContentLoader的一个问题是它在内部按顺序运行。 A better pattern is to parallelize the work and then sychronize at the end, so we get 更好的模式是先并行处理工作,然后最后进行同步,这样我们得到
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. 显然,如果任何任务都需要其他早期任务中的数据,则此方法将不起作用,但在大多数情况下应为您提供更好的总体吞吐量。