首页 文章

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

提问于
浏览
237

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

从我阅读/看到的文章/视频中,我知道 await async 不一定在后台线程上运行,并且需要在后台开始工作,需要等待 Task.Run(async () => ... ) . 使用 async await 不会阻止UI,但它仍然在UI线程上运行,因此它使它变得迟钝 .

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

我应该

  • 换行外部调用,因为这对.NET的线程工作较少

  • ,还是应该只包装内部运行_944122_的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.

广告(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 回答

  • 266

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

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

  • 5

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

    • 不要一次阻止UI线程超过50毫秒 .

    • 您可以在UI线程上每秒安排~100次连续; 1000太多了 .

    您应该使用两种技术:

    1) Use ConfigureAwait(false) when you can.

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

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

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

    2) Use Task.Run to call CPU-bound methods.

    您应该使用 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());
    

相关问题