我最近正在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。代码可以:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这是一样的吗
Foo foo = GetFooAsync(...).Result;
GetResult
的文档:“此类型及其成员旨在供编译器使用。”其他人不应该使用它。
async
/await
方法)
编辑:这是我 13 岁时写的,已经过时了。我推荐 Nitin Agarwal's answer。
差不多。不过有一个小区别:如果 Task
失败,GetResult()
将直接抛出异常,而 Task.Result
将抛出 AggregateException
。但是,当它是 async
时,使用其中任何一个有什么意义? 100 倍更好的选择是使用 await
。
此外,您不应该使用 GetResult()
。它仅供编译器使用,不适合您。但是,如果您不想要烦人的 AggregateException
,请使用它。
Task.GetAwaiter().GetResult()
优于 Task.Wait
和 Task.Result
,因为它传播异常而不是将它们包装在 AggregateException
中。但是,所有三种方法都可能导致死锁和线程池饥饿问题。应该避免它们都支持 async/await
。
下面的引用解释了为什么 Task.Wait
和 Task.Result
不简单地包含 Task.GetAwaiter().GetResult()
的异常传播行为(由于“非常高的兼容性条”)。
正如我之前提到的,我们有一个非常高的兼容性标准,因此我们避免了重大更改。因此,Task.Wait 保留其始终包装的原始行为。但是,您可能会发现自己处于某些高级情况下,您希望行为类似于 Task.Wait 所采用的同步阻塞,但是您希望原始异常被解包传播,而不是被封装在 AggregateException 中。为此,您可以直接定位任务的等待者。当您编写“await task;”时,编译器会将其转换为 Task.GetAwaiter() 方法的使用,该方法返回一个具有 GetResult() 方法的实例。当在故障任务上使用时,GetResult() 将传播原始异常(这就是“等待任务”获取其行为的方式)。因此,如果您想直接调用此传播逻辑,则可以使用“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 相冲突
Task.GetAwaiter().GetResult()
更等同于 Task.Wait
和 Task.Result
(因为这三个都将同步阻塞并有可能出现死锁),但 Task.GetAwaiter().GetResult()
具有 await 任务的异常传播行为。
https://github.com/aspnet/Security/issues/59
“最后一点:您应该尽可能避免使用 Task.Result 和 Task.Wait,因为它们总是将内部异常封装在 AggregateException 中,并将消息替换为通用异常(发生一个或多个错误),这使得调试更加困难. 即使不应该经常使用同步版本,您也应该强烈考虑改用 Task.GetAwaiter().GetResult()。”
Task.WhenAll(task1, task2).GetAwaiter().GetResult();
中丢失第二个任务。
另一个区别是当 async
函数只返回 Task
而不是 Task<T>
时,您不能使用
GetFooAsync(...).Result;
然而
GetFooAsync(...).GetAwaiter().GetResult();
仍然有效。
我知道问题中的示例代码适用于案例 Task<T>
,但是通常会提出问题。
Result
与 GetIntAsync()
一起使用,它返回 Task<int>
而不仅仅是 Task
。我建议您再次阅读我的答案。
GetFooAsync(...).Result
在返回 Task
的函数。现在这是有道理的,因为 C# 中没有 void 属性(Task.Result
是一个属性),但您当然可以调用 void 方法。
Task
没有返回值,因此我们认为 .Result
是一个错误。 task.GetAwaiter().GetResult()
仍然有效的事实是违反直觉的,值得强调一下。
如前所述,如果您可以使用 await
。如果您需要像您提到的 .GetAwaiter().GetResult()
、.Result
或 .Wait()
那样同步运行代码,正如许多人在评论/答案中所说的那样,存在死锁的风险。由于我们大多数人都喜欢 oneliners,因此您可以将它们用于 .Net 4.5<
通过异步方法获取值:
var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法
Task.Run(() => asyncMethod()).Wait();
使用 Task.Run
不会出现死锁问题。
资源:
https://stackoverflow.com/a/32429753/3850405
更新:
如果调用线程来自线程池,可能会导致死锁。会发生以下情况:一个新任务被排到队列的末尾,并且最终将执行该任务的线程池线程被阻塞,直到该任务被执行。
资源:
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
Task.Run
将工作卸载到 ThreadPool
,但我们仍在等待 this 线程以完成该工作。
.Result
或 .Wait()
的问题是,如果您阻止应该在任务上工作的线程,那么将没有线程来完成任务。您可以在此处阅读更多信息:medium.com/rubrikkgroup/…
我检查了 TaskOfResult.cs
的源代码(Source code of 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>
(Source code of GetAwaiter()), (Source code of TaskAwaiter) :
public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}
如果我们调用 TaskAwaiter<TResult>
的 GetResult()
方法,它会调用 Task.Result
属性,而 Task.Result
将调用 Task
的 Wait()
方法(Source code of GetResult()):
public TResult GetResult()
{
TaskAwaiter.ValidateEnd(m_task);
return m_task.Result;
}
它是 ValidateEnd(Task task)
( Source code of 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
更好的选择,因为后者不包含异常。
我在 C# 7 in a Nutshell (Joseph Albahari & Ben Albahari) 的第 582 页读到了这篇文章。
如果前面的任务出错,则在继续代码调用 awaiter.GetResult() 时重新抛出异常。我们可以简单地访问前件的 Result 属性,而不是调用 GetResult 。调用 GetResult 的好处是,如果前面的错误,则直接抛出异常,而不是包装在 AggregateException 中,从而允许更简单、更清晰的 catch 块。
来源:C# 7 in a Nutshell's page 582
如果任务出错,则在继续代码调用 awaiter.GetResult() 时重新抛出异常。我们可以简单地访问任务的 Result 属性,而不是调用 GetResult。调用 GetResult 的好处是,如果任务出错,则直接抛出异常,而不用包裹在 AggregateException 中,从而允许更简单、更清晰的 catch 块。对于非泛型任务,GetResult() 有一个 void 返回值。那么它的有用功能就是重新抛出异常。
来源:C# 7.0 简而言之
不定期副业成功案例分享
async Task
单元测试,并且已经有一段时间了。The 100x better option is to use await.
我讨厌这样的陈述,如果我可以在它前面拍await
我会的。但是,当我试图让异步代码与非异步代码一起工作时,例如在 Xamarin 中经常发生在我身上 a lot 的情况,我最终不得不在为了使它不会死锁UI。 编辑:我知道这已经过时了,但这并不能减轻我的挫败感,因为我发现在不能只使用await
的情况下没有替代方案的答案说明了这一点。