如果我不等待任务,会发生什么?

时间:2015-10-12 16:53:55

标签: c# async-await

考虑这个例子:

var task = DoSomething()
bool ready = await DoSomethingElse();
if (!ready) 
  return null;

var value = await DoThirdThing(); // depends on DoSomethingElse
return value + await task;

DoSomething做了一些可能需要一段时间的非常重要的工作,因此我们首先开始它 在此期间,我们会检查我们是否已准备好DoSomethingElse并提前退出。如果没有 如果我们是DoThirdThing,我们只会调用ready ,因为宇宙可能会爆炸。

我们无法使用Task.WhenAll因为DoThirdThing取决于DoSomethingElse,我们也不想等待DoSomething,因为我们想要同时调用其他两种方法可能的。

问题:如果我们不是task并提前退出,ready会发生什么?

SynchronizationContext会重新抛出它抛出的异常吗? 如果task正常完成,是否存在问题,因为没有人消耗它的值?

后续行动:是否有一种简洁的方法可以确保等待task

如果我们不是await task,我们可以ready,但如果有50个退出条件,这将非常繁琐。
可以将finally块用于await task并重新抛出潜在的异常吗?如果task正常完成,则会在finally块中再次等待,但这不会导致任何问题?

3 个答案:

答案 0 :(得分:18)

  

问题:如果我们没有准备好并提前退出,会发生什么?

无。代码忽略了任务,因此忽略了任务。

  

它会抛出的任何异常会被SynchronizationContext重新抛出吗?

没有。它们(最终)将被传递给TaskScheduler.UnobservedTaskException然后被忽略。

  

如果任务正常完成,是否存在问题,因为没有人消耗其值?

不。

  

跟进:是否有一种简洁的方法可以确保等待任务?

没有

  

是否可以使用finally块来等待任务并重新抛出潜在的异常?

是的,如果您的代码实际上是await的任务。据推测,这意味着在某处保存任务。

  

如果任务正常完成,则会在finally块中再次等待,但这不应该导致任何问题吗?

您可以根据需要await任务多次。

  

如果我们还没有准备好,我们可以简单地等待任务,但是如果有50个退出条件,这将非常繁琐。

然后考虑重组您的代码。

答案 1 :(得分:1)

  

跟进:是否有一种简洁的方法可以确保等待任务?

如果您需要对TaskScheduler.UnobservedTaskException 之类的任务抛出的异常进行单独控制,而不是await,则可以使用方便的工具:async void方法。

您的代码可能如下所示:

static async void Observe(Task task)
{        
    // use try/catch here if desired so;

    // otherwise, exceptions will be thrown out-of-band, i.e.
    // via SyncronizationContext.Post or 
    // via ThreadPool.QueueUSerWorkItem (if there's no sync. context) 

    await task; 
}

// ...

var taskObserved = false;
var task = DoSomething()
try
{
    bool ready = await DoSomethingElse();
    if (!ready) 
      return null;

    var value = await DoThirdThing(); // depends on DoSomethingElse
    taskObserved = true;
    return value + await task;
 }
 finally
 {
     if (!taskObserved)
        Observe(task);
 }

可以找到更多详细信息herehere

答案 2 :(得分:1)

<块引用>

问题:如果我们没有准备好并提前退出,任务会怎样?

该任务将被忽略,但如果您在任务下方嵌套了任务,您也可能会遇到以下错误。

Message: Object reference not set to an instance of an object.

StackTrace:    at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state)
   at System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state)
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

此错误将终止 Web 应用程序中的进程:NullReferenceException in System.Threading.Tasks calling HttpClient.GetAsync(url)

<块引用>

因为 TPL 试图重新加入一个已经被清除的上下文,所以它在 try/catch 之外抛出了 NullReferenceException。

<块引用>

这很难诊断。在生产环境中,您不会在 try/catch 中看到任何内容,而在 Visual Studio 中,等待被安排重新加入原始上下文的时间有些随机,这取决于 TaskScheduler 碰巧决定做什么。

来自 Microsoft 的一般建议是等待所有任务是最佳实践:

<块引用>

作为最佳做法,您应该始终等待呼叫。

如果你想要火而忘记推荐的模式在这里:Simple Fire and Forget