考虑这个例子:
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
块中再次等待,但这不会导致任何问题?
答案 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);
}
答案 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