正确使用任务时。运行,当只是async-await

我想问你关于何时使用Task.Run的正确架构的意见。我正在经历滞后的用户界面在我们的WPF .NET 4.5 应用程序(使用Caliburn Micro框架).

基本上我在做(非常简化的代码片段):

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线程上运行,因此它会使UI线程延迟。

哪里是放置Task.Run的最佳位置?

我应该

  1. 包装外部调用,因为这减少了。net的线程工作

  2. ,或者我应该只包装cpu绑定的方法内部运行Task.Run,因为这使得它可用于其他地方?我不确定在内核深处开始后台线程的工作是否是一个好主意。

Ad(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),第二个解为:

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
}
373255 次浏览

注意我博客上收集的在UI线程上执行工作的指导方针:

  • 不要一次阻塞UI线程超过50ms。
  • 你可以在UI线程上安排每秒100次的延续;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());

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

显然,如果任何任务需要来自其他早期任务的数据,这就行不通了,但是对于大多数场景,这应该会为您提供更好的总体吞吐量。