正确使用Task.Run时和async-await时

时间:2013-08-02 09:35:29

标签: c# asynchronous task async-await

我想问你关于何时使用Task.Run的正确架构的意见。我在WPF .NET 4.5中遇到了滞后的UI 应用程序(使用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不一定在后台线程上运行,并且需要在后台开始工作,需要等待Task.Run(async () => ... ) 1}}。使用async await不会阻止用户界面,但它仍然在UI线程上运行,因此它会让它变得迟钝。

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

我应该

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

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

  3. 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
    }
    

2 个答案:

答案 0 :(得分:312)

请注意我在博客上收集的guidelines for performing work on a UI thread

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

您应该使用两种技术:

1)尽可能使用ConfigureAwait(false)

,例如,await MyAsync().ConfigureAwait(false);代替await MyAsync();

ConfigureAwait(false)告诉await您不需要在当前上下文中恢复(在这种情况下,“在当前上下文中”表示“在UI线程上”)。但是,对于async方法的其余部分(在ConfigureAwait之后),您无法执行任何假设您处于当前上下文中的操作(例如,更新UI元素)。

有关详细信息,请参阅我的MSDN文章Best Practices in Asynchronous Programming

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

答案 1 :(得分:9)

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

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