Task.Run
的正确体系结构的意见。我在WPF .NET 4.5应用程序(使用Caliburn Micro框架)中遇到了缓慢的UI。我基本上在做(非常简化的代码段):
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();
}
}
从我阅读/看到的文章/视频中,我知道
await
async
不一定在后台线程上运行,并且要在后台开始工作,您需要用await Task.Run(async () => ... )
包装它。使用async
await
不会阻止UI,但仍在UI线程上运行,因此使它滞后。放Task.Run的最佳位置在哪里?
我应该
包装外部调用,因为这对于.NET来说线程处理工作较少,还是应该只包装内部以
Task.Run
运行的CPU绑定方法为这样可以在其他地方重用吗?我不确定在内核中深入后台线程是否是一个好主意。广告(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.
广告(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 楼
请注意在我的博客上收集的有关在UI线程上执行工作的准则:一次阻塞UI线程的时间不要超过50毫秒。
您可以安排每秒〜100个UI线程连续性; 1000太多了。
应该使用两种技术:
1)尽可能使用
ConfigureAwait(false)
。例如,用await MyAsync().ConfigureAwait(false);
代替await MyAsync();
。ConfigureAwait(false)
告诉await
不需要在当前上下文上恢复(在这种情况下,“在当前上下文上”表示“在UI线程上”)。但是,对于其余async
方法(在ConfigureAwait
之后),您不能做任何假设您处于当前上下文的操作(例如,更新UI元素)。有关更多信息,请参见MSDN文章“异步编程最佳实践”。
2)使用
Task.Run
调用受CPU约束的方法。您应该使用
Task.Run
,但不要在要重用的任何代码中使用(即库代码)。因此,您使用Task.Run
来调用该方法,而不是将其作为该方法的实现的一部分。因此,纯受CPU约束的工作应如下所示:
// Documentation: This method is CPU-bound.
void DoWork();
您将使用
Task.Run
进行调用:await Task.Run(() => DoWork());
混合了CPU绑定和I / O绑定的方法应具有
Async
签名,并带有指出其CPU限制性质的文档:// Documentation: This method is CPU-bound.
Task DoWorkAsync();
您还可以使用
Task.Run
调用它(由于它部分受CPU限制):await Task.Run(() => DoWorkAsync());
评论
感谢您的快速回复!我知道您发布的链接,并看到了博客中引用的视频。实际上,这就是我发布此问题的原因-在视频中说(与您的回复相同),您不应在核心代码中使用Task.Run。但是我的问题是,我每次使用该方法时都需要包装这种方法,以免减慢响应速度(请注意,我所有的代码都是异步的并且没有阻塞,但是如果没有Thread.Run,它就很麻烦)。我是否只是将CPU绑定的方法(许多Task.Run调用)包装或完全将所有内容包装在一个Task.Run中是更好的方法?
–卢卡斯K
13年8月2日在14:16
您的所有库方法都应使用ConfigureAwait(false)。如果先执行此操作,则可能会发现Task.Run完全不必要。如果您仍然需要Task.Run,那么无论您调用一次还是多次,它都不会对运行时造成太大影响,因此只需执行最自然的代码即可。
–斯蒂芬·克莱里(Stephen Cleary)
2013年8月2日14:21
我不了解第一种技术将如何帮助他。即使在cpu绑定方法上使用ConfigureAwait(false),仍然是UI线程将执行cpu绑定方法,并且只有之后的所有操作都可以在TP线程上完成。还是我误会了什么?
–德雷克
2015年10月5日19:46
@ user4205580:否,Task.Run可以理解异步签名,因此在DoWorkAsync完成之前它不会完成。不需要额外的异步/等待。我将在Task.Run礼节的博客系列中解释更多“为什么”的信息。
–斯蒂芬·克莱里(Stephen Cleary)
15年11月14日在16:17
@ user4205580:否。绝大多数“核心”异步方法不在内部使用它。实施“核心”异步方法的通常方法是使用TaskCompletionSource
–斯蒂芬·克莱里(Stephen Cleary)
15年11月14日在21:08
#2 楼
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);
}
}
显然,如果任何任务都需要来自其他早期任务,但在大多数情况下应该可以为您提供更好的总体吞吐量。
评论
顺便说一句,(1)中的行等待Task.Run(async()=>等待this.contentLoader.LoadContentAsync());应该只是等待Task.Run(()=> this.contentLoader.LoadContentAsync());。 AFAIK通过在Task.Run中添加第二次等待和异步不会获得任何收益。而且由于您没有传递参数,因此稍稍简化了等待Task.Run(this.contentLoader.LoadContentAsync);.如果您有第二次等待,实际上会有一点差异。请参阅本文。我发现它非常有用,只是在这一点上我并不同意,宁愿直接返回Task而不是等待。 (如您在评论中所建议)
如果只有一系列同步方法,则可以使用等待模式Task.Run(()=> {RunAnySynchronousMethod(); return Task.CompletedTask;});在您的异步方法(例如异步控制器方法,测试方法等)中。