381

我想请教您对何时使用正确架构的看法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不一定在后台线程上运行,并且要在后台开始工作,您需要用 await 包装它Task.Run(async () => ... )。Usingasync await不会阻塞 UI,但它仍然在 UI 线程上运行,所以它使它变得滞后。

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

我应该只是

  1. 包装外部调用,因为这对 .NET 的线程工作较少

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

广告(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
}
4

2 回答 2

432

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

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

您应该使用两种技术:

1) 尽可能使用ConfigureAwait(false)

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

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

有关详细信息,请参阅我的 MSDN 文章异步编程中的最佳实践

2)Task.Run用于调用 CPU-bound 方法。

您应该使用Task.Run,但不要在您希望可重用的任何代码(即库代码)中使用。所以你使用Task.Run调用方法,而不是作为方法执行的一部分。

所以纯粹受 CPU 限制的工作看起来像这样:

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

你会调用使用Task.Run

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

混合了 CPU-bound 和 I/O-bound 的方法应该有一个Async签名,其中的文档指出它们的 CPU-bound 性质:

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

您还可以调用 using Task.Run(因为它部分受 CPU 限制):

await Task.Run(() => DoWorkAsync());
于 2013-08-02T11:17:56.407 回答
17

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

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

于 2017-07-02T11:09:42.973 回答