正确使用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?
我应该吗?
-
包装外部调用,因为这是较less的线程工作
-
,还是应该只包装内部运行
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
…在代码的前端,因为这是击败的目的。 如果Task
是async
那么你应该能够只是调用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); } }
显然,如果任何任务需要来自其他早期任务的数据,则这不起作用,但是应该为大多数场景提供更好的整体吞吐量。