446

我最近正在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。代码可以:

Foo foo = GetFooAsync(...).GetAwaiter().GetResult();

这是一样的吗

Foo foo = GetFooAsync(...).Result;
4

7 回答 7

235

差不多。但是有一个小区别:如果Task失败,GetResult()只会抛出直接导致的异常,而Task.Result会抛出AggregateException. 但是,当它是时使用其中任何一个有什么意义async?100 倍更好的选择是使用await.

另外,您不应该使用GetResult(). 它仅供编译器使用,不适合您。但是,如果您不想烦人AggregateException,请使用它。

于 2013-06-24T20:34:19.537 回答
220

Task.GetAwaiter().GetResult()优于Task.Wait并且Task.Result因为它传播异常而不是将它们包装在AggregateException. 但是,所有三种方法都可能导致死锁和线程池饥饿问题。他们都应该避免而赞成async/await

下面的引用解释了为什么Task.Wait并且Task.Result不简单地包含Task.GetAwaiter().GetResult()(由于“非常高的兼容性条”)的异常传播行为。

正如我之前提到的,我们有一个非常高的兼容性标准,因此我们避免了重大更改。因此,Task.Wait保留其始终包装的原始行为。Task.Wait但是,您可能会发现自己处于某些高级情况下,您希望行为类似于AggregateException. 为此,您可以直接定位任务的等待者。当您编写“<code>await task;”时,编译器会将其转换为方法的使用,该Task.GetAwaiter()方法返回一个具有GetResult()方法的实例。在故障任务上使用时,GetResult()将传播原始异常(这是“<code>await task;”获取其行为的方式)。因此,如果您想直接调用此传播逻辑,则可以使用“<code>task.GetAwaiter().GetResult()”。

https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

“<code>GetResult”实际上意味着“检查任务是否有错误”</p>

一般来说,我尽量避免同步阻塞异步任务。但是,在少数情况下我确实违反了该准则。在那些罕见的情况下,我首选的方法是GetAwaiter().GetResult()因为它保留了任务异常,而不是将它们包装在AggregateException.

http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html

于 2016-07-22T15:27:06.863 回答
74

https://github.com/aspnet/Security/issues/59

“最后一点:你应该尽可能避免使用Task.ResultTask.Wait,因为它们总是将内部异常封装在一个中 AggregateException,并用一个通用的(发生一个或多个错误)替换消息,这使得调试更加困难。即使同步版本应该'不经常使用,你应该强烈考虑Task.GetAwaiter().GetResult()改为使用。”

于 2015-07-31T08:59:16.690 回答
42

另一个区别是当async函数返回Task而不是Task<T>then 你不能使用

GetFooAsync(...).Result;

然而

GetFooAsync(...).GetAwaiter().GetResult();

仍然有效。

我知道问题中的示例代码是针对这种情况的Task<T>,但是通常会问这个问题。

于 2017-03-03T11:47:41.613 回答
31

如前所述,如果您可以使用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

于 2019-01-24T15:12:07.970 回答
15

我检查了TaskOfResult.csTaskOfResult.cs的源代码)的源代码:

如果Task没有完成,Task.Result会调用Task.Wait()中的方法getter

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);
        }
    }
}

如果我们调用GetAwaiter的方法TaskTask将包装TaskAwaiter<TResult>GetAwaiter()的源代码),(TaskAwaiter的源代码):

public TaskAwaiter GetAwaiter()
{
    return new TaskAwaiter(this);
}

如果我们调用GetResult()的方法TaskAwaiter<TResult>,它会调用Task.Result属性,这Task.Result将调用Wait()方法TaskGetResult()的源代码):

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因为它不包含异常。

我在 Nutshell (Joseph Albahari & Ben Albahari) 书中C# 7 的第 582 页读到了这篇文章

如果前面的任务出错,则在继续代码调用时重新抛出异常 awaiter.GetResult()GetResult我们可以简单地访问前件的 Result 属性,而不是调用 。调用的好处 GetResult是,如果前件出错,则直接抛出异常而不用包裹在 中 AggregateException,从而允许更简单、更清晰的 catch 块。

资料来源:C# 7 in a Nutshell's page 582

于 2021-02-17T06:21:48.520 回答
2

如果任务出错,则在继续代码调用 awaiter.GetResult() 时重新抛出异常。我们可以简单地访问任务的 Result 属性,而不是调用 GetResult。调用 GetResult 的好处是,如果任务出错,则直接抛出异常,而不用包裹在 AggregateException 中,从而允许更简单、更清晰的 catch 块。

对于非泛型任务,GetResult() 有一个 void 返回值。那么它的有用功能就是重新抛出异常。

来源:C# 7.0 简而言之

于 2019-06-13T09:05:41.803 回答