正确使用Task.Run时,只是asynchronous等待

我想问你关于何时使用Task.Run的正确架构的Task.Run 。 我在WPF .NET 4.5应用程序(Caliburn Micro框架)中遇到了laggy 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(); } } 

从我读/看到的文章/video,我知道await async不一定在后台线程上运行,并开始在后台工作,你需要包装与等待Task.Run(async () => ... ) 。 使用async await不会阻塞用户界面,但它仍然在UI线程上运行,所以它使得它很滞后。

哪里是最好的地方把Task.Run?

我应该吗?

  1. 包装外部调用,因为这是较less的线程工作

  2. ,还是应该只包装内部运行Task.Run CPU绑定方法,因为这使得它可以重用的其他地方? 我不确定在这里如果开始在后台线程深入核心是一个好主意。

广告(1),第一个解决scheme是这样的:

 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),第二个解决scheme是这样的:

 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 } 

请注意在我的博客上收集的有关在UI线程上执行工作的指导原则 :

  • 不要一次阻止超过50ms的UI线程。
  • 您可以每秒在UI线程上安排约100个延续; 1000太多了。

有两种技巧你应该使用:

1)尽可能使用ConfigureAwait(false)

例如, await MyAsync().ConfigureAwait(false); 而不是await MyAsync();

ConfigureAwait(false)告诉await你不需要在当前上下文恢复(在这种情况下,“在当前上下文”的意思是“在UI线程上”)。 但是,对于该async方法的其余部分(在ConfigureAwait ),您无法做任何假定您处于当前上下文(例如,更新UI元素)的操作。

有关更多信息,请参阅我的MSDN文章asynchronous编程最佳实践 。

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可以实现没有Task.Run …在代码的前端,因为这是击败的目的。 如果Taskasync那么你应该能够只是调用await TaskAsync() …这是一个很好的例子的git 仓库 : AsyncAwaitTasksExample 。 它在UI上有一个实时计时器,各种Tasks选项以及使用方法。 唯一有效的是类似于下面。

 Task<String> GetStringAsync() { return Task.Run<String>(() => { return "Hello World"; }); } //Run the task with Async await void async SomeFunction() { var msg = await GetStringAsync(); } 

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); } } 

显然,如果任何任务需要来自其他早期任务的数据,则这不起作用,但是应该为大多数场景提供更好的整体吞吐量。