【问题标题】:How do I start not-awaited background tasks in an async method?如何以异步方法启动未等待的后台任务?
【发布时间】:2016-02-23 09:42:42
【问题描述】:

我正在努力解决如何在异步方法的世界中执行一些非常长时间运行的后台处理。

使用来自Stephen Cleary's blog 的词汇,我有兴趣在await 之后启动“委托任务” - 执行“承诺任务”。我想尽快返回承诺的值,并让委托任务在后台继续执行。

为了处理 un-await-ed 委托任务中的异常,我将使用仿照 the one described here. 建模的异常处理延续

public async Task<int> ComplexProcessAsync()
{
  ...
  int firstResult = await FirstStepAsync();

  // THE COMPILER DOESN'T LIKE THIS
  Task.Run(() => VeryLongSecondStepIDontNeedToWaitFor(firstResult)).LogExceptions();

  return firstResult;
}

由于VeryLongSecondStep...() 可能需要很多分钟,并且其结果完全可以通过副作用(例如出现在数据库中的记录)来观察,所以我reeeaaalllyyawait它。

但正如所指出的,编译器不喜欢这样。它警告"Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call."

我可以忽略警告,但我觉得我没有以“正确”的方式执行此操作。那么正确的方法是什么

谢谢!

【问题讨论】:

  • 您是否在 ASP.NET 应用程序中执行此操作?
  • @WilliamXifaras 这有关系吗?我知道 SynchronizationContext 和 TaskScheduler 可能不同,但这有意义吗?
  • 重要的是我为您提供了一个适用于 ASP.NET 的选项。
  • @WilliamXifaras 我的应用程序是一个自托管的 Web API。

标签: c# .net async-await


【解决方案1】:

我可以忽略警告,但我觉得我没有以“正确”的方式执行此操作。那么正确的做法是什么?

编译器警告您您没有在等待任务。根据我的经验,99% 以上的编译器是正确的,代码是错误的,因为它缺少 await。所以这是罕见的情况之一,你知道自己在做什么并且想要危险地生活。 :)

在这种情况下,您可以将任务显式分配给未使用的变量:

var _ = Task.Run(() => VeryLongSecondStepIDontNeedToWaitFor(firstResult));

编译器足够智能,可以理解您正在使用此变量来消除“缺少等待”警告。

顺便说一句,我完全不推荐ContinueWith;在异步世界中,它只是await 的一个更危险的版本。所以我会写LogExceptions 这样的东西:

public static async Task LogExceptions(this Task task)
{
  try
  {
    await task.ConfigureAwait(false);
  }
  catch (Exception ex)
  {
    LogException(ex);
  }
}

【讨论】:

    【解决方案2】:

    由于这是在 ASP.NET Web API 应用程序中,我建议您使用 HostingEnvironment.QueueBackgroundWorkItem。 Stephen Cleary 也有一篇关于它的帖子。

            HostingEnvironment.QueueBackgroundWorkItem(ct =>
            {
                try
                {
                    // your code
                }
                catch (Exception ex)
                {
                    // handle & log exception
                }
            });
    

    https://msdn.microsoft.com/en-us/library/dn636893(v=vs.110).aspx http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html

    【讨论】:

    • 感谢您的建议。如果应用程序不是 ASP.NET 应用程序怎么办?比如说,业务线 WinForms 的事情?
    • @David Rubin 我不确定,因为我不是 WinForms 开发人员。我得研究一下。
    • @GianPaolo 没关系,除了提到的here 和回应here,“任务现在是将工作排队到线程池的首选方式。”这就是我正在寻找的解决方案的主旨 - 如何使用我不会等待的任务系统对后台任务进行排队,并最有效地完成它。
    • 支持 Stephen Cleary 链接,该链接明确指出“正确的解决方案是使用基本的分布式架构”。 ASP.NET 的主机可以并且将终止长时间运行的进程,即使在使用 QBWI 时也是如此,但有时这是可以接受的。
    【解决方案3】:

    编译器试图保护你免受错误,因为当你真正需要等待时很容易忘记await,但在这种情况下你真的不需要await,所以你可以忽略警告。

    但是,在这种情况下,我认为在线程池中的某个线程上运行任务是一个更好的主意,因为如果您像现在这样在主线程上运行它,您可能会导致您的应用程序对新的传入请求/事件无响应。

    【讨论】:

      【解决方案4】:

      我建议您将VeryLongSecondStepIDontNeedToWaitFor() 移出ComplexProcessAsync() 方法。

      ComplexProcessAsync() 本身返回一个值,该值被至少两个任务使用(其中一个是 Task.Run(() => VeryLongSecondStepIDontNeedToWaitFor(firstResult)))。

      您可以通过Task.WhenAny() 组合这些任务:

      var result = await ComplexProcessAsync();
      await Task.WhenAny(VeryLongSecondStepIDontNeedToWaitFor(firstResult), YourSecondMethod(firstResult))
      

      其中 YourSecondMethod(firstResult) 是使用 ComplexProcessAsync() 结果的方法。

      【讨论】:

      • 感谢您的建议,但请考虑SecondStep()FirstStepAsync() 的重要后续的情况。也就是说,FirstStepAsync()SecondStep() 必须 总是一起出现,因此它们在 ComplexProcessAsync() 中被包装在一起。
      • 但现在他们没有了。首先进入 FirstStep,程序等待它完成,然后启动 VeryLongSecondStep。
      猜你喜欢
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-23
      相关资源
      最近更新 更多