我想问一下您对使用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来包装它。运行(async() =>…). 使用async await并不会阻塞UI,但它仍然运行在UI线程上,因此它会使UI线程延迟。

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

我应该

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

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
}

当前回答

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

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

其他回答

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

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

你应该使用两种技巧:

1)尽可能使用ConfigureAwait(false)。

例如,await MyAsync(). configureawait (false);而不是等待MyAsync();。

ConfigureAwait(false)告诉await,您不需要在当前上下文中恢复(在本例中,“在当前上下文中”意味着“在UI线程上”)。然而,对于该异步方法的其余部分(在ConfigureAwait之后),您不能做任何假设您处于当前上下文中的事情(例如,更新UI元素)。

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

2)使用任务。运行调用cpu绑定的方法。

您应该使用Task。运行,但不要在任何您想要重用的代码中运行(即,库代码)。所以你使用Task。运行以调用方法,而不是作为方法实现的一部分。

所以纯cpu的工作是这样的:

// Documentation: This method is CPU-bound.
void DoWork();

您可以使用Task调用它。运行:

await Task.Run(() => DoWork());

混合了cpu绑定和I/ o绑定的方法应该有一个Async签名,并在文档中指出它们的cpu绑定特性:

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

也可以使用Task调用它。运行(因为它部分受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);
    }
}

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