我最近在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。这段代码执行以下操作:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这是一样的吗
Foo foo = GetFooAsync(...).Result;
我最近在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。这段代码执行以下操作:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这是一样的吗
Foo foo = GetFooAsync(...).Result;
Task.GetAwaiter().GetResult()
优于Task.Wait
和Task.Result
,因为它传播异常而不是将其包装在AggregateException
中。然而,这三种方法都会导致死锁和线程池耗尽问题。应该避免使用它们,转而使用async/await
。
下面的引用解释了为什么Task.Wait
和Task.Result
没有简单地包含Task.GetAwaiter().GetResult()
的异常传播行为(由于“非常高的兼容性标准”)。
Task.Wait
保留了其始终包装的原始行为。但是,在某些高级情况下,您可能希望获得类似于Task.Wait
所使用的同步阻塞的行为,但是您希望原始异常传播而不是被包含在AggregateException
中。为了实现这一点,您可以直接针对任务的awaiter进行操作。当您编写“await task;
”时,编译器将其转换为使用Task.GetAwaiter()
方法的用法,该方法返回一个具有GetResult()
方法的实例。在故障的任务上使用GetResult()
将传播原始异常(这就是“await task;
”如何获得其行为的方式)。因此,如果您想直接调用此传播逻辑,可以使用“task.GetAwaiter().GetResult()
”。
https://devblogs.microsoft.com/pfxteam/task-exception-handling-in-net-4-5/
“GetResult
” 实际上意味着“检查任务是否存在错误”。一般情况下,我尽力避免在异步任务上同步阻塞。然而,在少数情况下,我会违反这个指导方针。在那些罕见的情况下,我的首选方法是使用 GetAwaiter().GetResult()
,因为它保留了任务异常,而不是将它们包装在一个 AggregateException
中。
https://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html
Task.GetAwaiter().GetResult()
等同于await task
。我认为第一个选项在方法无法标记为async
(例如构造函数)时使用。如果是这样,那么它与@It'sNotALie的最佳答案产生了冲突。 - OlegITask.GetAwaiter().GetResult()
更类似于 Task.Wait
和 Task.Result
(因为它们三个都会同步阻塞并有潜在死锁风险),但是 Task.GetAwaiter().GetResult()
具有等待异步任务时异常传播的行为。 - Nitin Agarwal编辑:本文写于我13岁时,已经过时。我建议使用Nitin Agarwal的答案代替。
基本上是这样的。不过有一个小差别:如果Task
失败了,GetResult()
会直接抛出引起失败的异常,而Task.Result
会抛出一个AggregateException
。然而,既然是async
,为什么要使用它们中的任何一个呢?100倍更好的选择是使用await
。
另外,你不应该使用GetResult()
。它只是用于编译器的,不是给你用的。但如果你不想要烦人的AggregateException
,可以使用它。
await
。我讨厌这样的说法,如果可以,我会在前面加上await
。但是,在我尝试使异步代码与非异步代码(就像在Xamarin中经常发生的情况)协同工作时,我经常不得不使用ContinueWith
等方法来防止死锁UI。编辑:我知道这很旧了,但这并不能减轻我的沮丧,因为找到没有替代方案的答案无法解决在某些情况下你不能简单地使用await
的问题。 - Thomas F.https://github.com/aspnet/Security/issues/59
“最后一个提示:尽量避免使用
Task.Result
和Task.Wait
,因为它们总是将内部异常封装在AggregateException
中,并用通用消息(出现了一个或多个错误)替换原始消息,这使得调试更加困难。即使不应该经常使用同步版本,您也应该强烈考虑改用Task.GetAwaiter().GetResult()
。”
Task.WhenAll(task1, task2).GetAwaiter().GetResult();
时将会失去第二个任务。 - Monsignor另一个区别在于当 async
函数返回的仅是 Task
而不是 Task<T>
时,您将无法使用
GetFooAsync(...).Result;
然而
GetFooAsync(...).GetAwaiter().GetResult();
仍然有效。
我知道问题中的示例代码是针对Task<T>
的情况,但是问题是一般性的。
GetIntAsync()
函数的Result
属性,它返回的是Task<int>
而不仅仅是Task
。我建议您再次阅读我的答案。 - Nuri TasdemirTask
的函数内部无法使用 GetFooAsync(...).Result
。现在我明白了,因为在 C# 中没有 void 属性(Task.Result
是一个属性),但是你当然可以调用 void 方法。 - wojciech_rak正如先前提到的,如果您可以使用await
。如果您需要像您提到的那样同步运行代码,.GetAwaiter().GetResult()
,.Result
或.Wait()
会有死锁风险,正如许多评论/答案中所说的那样。由于大多数人喜欢一行代码,因此您可以在.Net 4.5<
中使用这些。
通过异步方法获取值:
var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法
Task.Run(() => asyncMethod()).Wait();
由于使用Task.Run
,不会发生死锁问题。Task.Run
将工作转移到了 ThreadPool
,但我们仍然在 这个 线程上等待该工作完成。 - Mike.Result
或.Wait()
的问题在于如果您阻塞了应该处理任务的线程,那么就没有线程来完成任务。您可以在此处了解更多信息:https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d - OgglasTaskOfResult.cs
的源代码(TaskOfResult.cs的源代码):Task
没有完成,Task.Result
将在getter
中调用Task.Wait()
方法。public TResult Result
{
get
{
// If the result has not been calculated yet, wait for it.
if (!IsCompleted)
{
// We call NOCTD for two reasons:
// 1. If the task runs on another thread, then we definitely need to notify that thread-slipping is required.
// 2. If the task runs inline but takes some time to complete, it will suffer ThreadAbort with possible state corruption.
// - it is best to prevent this unless the user explicitly asks to view the value with thread-slipping enabled.
//#if !PFX_LEGACY_3_5
// Debugger.NotifyOfCrossThreadDependency();
//#endif
Wait();
}
// Throw an exception if appropriate.
ThrowIfExceptional(!m_resultWasSet);
// We shouldn't be here if the result has not been set.
Contract.Assert(m_resultWasSet, "Task<T>.Result getter: Expected result to have been set.");
return m_result;
}
internal set
{
Contract.Assert(m_valueSelector == null, "Task<T>.Result_set: m_valueSelector != null");
if (!TrySetResult(value))
{
throw new InvalidOperationException(Strings.TaskT_TransitionToFinal_AlreadyCompleted);
}
}
}
Task
的GetAwaiter
方法,Task
将会包装TaskAwaiter<TResult>
(GetAwaiter()源代码),(TaskAwaiter源代码):public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}
TaskAwaiter<TResult>
的GetResult()
方法,它将调用Task.Result
属性,而Task.Result
将调用Task
的Wait()
方法( GetResult()源代码):public TResult GetResult()
{
TaskAwaiter.ValidateEnd(m_task);
return m_task.Result;
}
ValidateEnd(Task task)
的源代码(ValidateEnd(Task task)的源代码):internal static void ValidateEnd(Task task)
{
if (task.Status != TaskStatus.RanToCompletion)
HandleNonSuccess(task);
}
private static void HandleNonSuccess(Task task)
{
if (!task.IsCompleted)
{
try { task.Wait(); }
catch { }
}
if (task.Status != TaskStatus.RanToCompletion)
{
ThrowForNonSuccess(task);
}
}
GetResult()
调用了 TaskAwaiter.ValidateEnd(...)
,因此 Task.Result
与 GetAwaiter.GetResult()
不同。GetAwaiter().GetResult()
是一个更好的选择,而不是使用 .Result
,因为前者不会包装异常。awaiter.GetResult()
时,异常将被重新抛出。我们可以直接访问前置任务的 Result 属性,而不是调用 GetResult
。调用 GetResult
的好处是,如果前置任务出现故障,异常将直接抛出,而不会被包装在 AggregateException
中,从而使捕获块更简单、更清晰。
GetResult
的文档中可以看到:“此类型及其成员旨在供编译器使用。” 其他人不应该使用它。 - spenderasync
/await
方法),它可能会立即死锁。 - Marc Gravellasync
方法中。如果必须使用同步调用,请使用ConfigureAwait(false)
来避免出现上下文切换的开销,从而提高性能。 - I4V