即发即弃的异步任务方法有时不调用



我有一个async方法:

public async Task DoSomethingAsync(){
    ...
    await ...
    await ...
    ....
    return await SaveAsync();
}
在大多数情况下,我调用这个方法:
await DoSomethingAsync()

此调用按预期工作。但在某处,我需要调用这个方法为fire,而忘记了:

public void OtherMethod()
{
    ...
    DoSomethingAsync(); //fire and forget here
}

在这种情况下,有时任务DoSomethingAsync()运行并完成,但有时任务从未被调用(或调用DoSomethingAsync()中的某些await,但从未完成最后一个await SaveAsync();)。

我正在努力确保任务将被调用在火和忘记的方式通过这些代码:

public void OtherMethod()
{
    ...
    Task.Factory.StartNew(() =>
    {
        await DoSomethingAsync();
    }); //fire and forget again here
}

然而,这并不像预期的那样工作。所以我的问题是:

  1. 如何使呼叫DoSomethingAsync()没有await将始终运行和完成?

  2. 如果我删除DoSomethingAsync()内的所有async/await代码,并用.ContinueWith()替换await,那么对Task DoSomethingAsync()的调用(在方法声明中没有async)将被调用并确保完成(忽略AppDomain重启/崩溃的情况),如果是,从调用多长时间(我不认为如果任务将在10分钟后调用我会很高兴)?

您可能在DoSomethingAsync的某个地方得到一个异常,您无法观察到,因为您忽略了任务。这正是你所要求的行为,因为你在告诉代码"忘记"任务。

要观察异常,您不能"忘记"任务:

public Task OtherMethodAsync()
{
  ...
  return DoSomethingAsync();
}

在某个点,await(或Wait)返回任务。

等待应该工作得很好,所以这里可能有其他事情阻碍了一个或多个等待的方法。有以下几种可能性:

  1. 你的某个方法发生了死锁,阻止了它的完成并阻止了等待的恢复。
  2. 所有的线程池线程由于某种原因阻塞,阻止了挂起的任务运行。
  3. 你正在同步上下文中运行async方法,并且有东西阻碍了上下文,阻止它运行已调度的回调。通常情况下,上下文是UI线程,通常这是很明显的,因为它锁定了你的应用程序UI。

如果你可以连接VS调试器并观察发生了什么,尝试暂停并查看并行堆栈视图。这应该有助于缩小考虑的可能性。


正如Stephen指出的,当你调用它时,也有可能发生一个Exception。如果你还没有,确保你已经处理了TaskScheduler。UnobservedTaskException记录任何类似的事件。注意,这是从终结器调用的,因此它被调用的时间是不确定的。这可能会使调试变得棘手,因为事件可能要比导致异常的实际事件晚得多才会触发。因此,我建议遵循Stephen的建议,将任务保存为等待或稍后在其他地方等待。

. NET通常不允许在请求中启动"即发即弃"(async void)操作。有关此行为的进一步解释和一些可能的解决方法,请参阅https://stackoverflow.com/a/22484832/59641。

最新更新