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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18013523/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 11:02:50  来源:igfitidea点击:

When correctly use Task.Run and when just async-await

c#asynchronoustaskasync-await

提问by Lukas K

I would like to ask you on your opinion about the correct architecture when to use Task.Run. I am experiencing laggy UI in our WPF .NET 4.5 application (with Caliburn Micro framework).

我想请教一下您对何时使用Task.Run. 我在我们的 WPF .NET 4.5 应用程序(使用 Caliburn Micro 框架)中遇到了滞后的 UI。

Basically I am doing (very simplified code snippets):

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

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

From the articles/videos I read/saw, I know that awaitasyncis not necessarily running on a background thread and to start work in the background you need to wrap it with await Task.Run(async () => ... ). Using asyncawaitdoes not block the UI, but still it is running on the UI thread, so it is making it laggy.

从我阅读/看到的文章/视频中,我知道它awaitasync不一定在后台线程上运行,并且要在后台开始工作,您需要用 await 包装它Task.Run(async () => ... )。使用asyncawait不会阻塞 UI,但它仍然在 UI 线程上运行,所以它使它变得滞后。

Where is the best place to put Task.Run?

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

Should I just

我应该只是

  1. Wrap the outer call because this is less threading work for .NET

  2. , or should I wrap only CPU-bound methods internally running with Task.Runas this makes it reusable for other places? I am not sure here if starting work on background threads deep in core is a good idea.

  1. 包装外部调用,因为这对于 .NET 来说是更少的线程工作

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

Ad (1), the first solution would be like this:

广告(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), the second solution would be like this:

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

采纳答案by Stephen Cleary

Note the guidelines for performing work on a UI thread, collected on my blog:

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

  • Don't block the UI thread for more than 50ms at a time.
  • You can schedule ~100 continuations on the UI thread per second; 1000 is too much.
  • 一次不要阻塞 UI 线程超过 50 毫秒。
  • 您可以每秒在 UI 线程上安排约 100 个延续;1000太多了。

There are two techniques you should use:

您应该使用两种技术:

1) Use ConfigureAwait(false)when you can.

1) 尽可能使用ConfigureAwait(false)

E.g., await MyAsync().ConfigureAwait(false);instead of await MyAsync();.

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

ConfigureAwait(false)tells the awaitthat you do not need to resume on the current context (in this case, "on the current context" means "on the UI thread"). However, for the rest of that asyncmethod (after the ConfigureAwait), you cannot do anything that assumes you're in the current context (e.g., update UI elements).

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

For more information, see my MSDN article Best Practices in Asynchronous Programming.

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

2) Use Task.Runto call CPU-bound methods.

2)Task.Run用于调用受 CPU 限制的方法。

You should use Task.Run, but not within any code you want to be reusable (i.e., library code). So you use Task.Runto callthe method, not as part of the implementationof the method.

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

So purely CPU-bound work would look like this:

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

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

Which you would call using Task.Run:

您将使用Task.Run以下方法调用:

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

Methods that are a mixtureof CPU-bound and I/O-bound should have an Asyncsignature with documentation pointing out their CPU-bound nature:

方法是一个混合的CPU绑定和I / O密集型应该有一个Async与文件指出他们的CPU绑定性质签名:

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

Which you would also call using Task.Run(since it is partially CPU-bound):

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

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

回答by Paul Hatcher

One issue with your ContentLoader is that internally it operates sequentially. A better pattern is to parallelize the work and then sychronize at the end, so we get

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

Obviously, this doesn't work if any of the tasks require data from other earlier tasks, but should give you better overall throughput for most scenarios.

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