使用 await Task<T> 和 Task<T>.Result 有什么区别?

83
public async Task<string> GetName(int id)
{
    Task<string> nameTask = Task.Factory.StartNew(() => string.Format("Name matching id {0} = Developer", id));
    return nameTask.Result;
}

在上面的方法的返回语句中,我使用了Task<T>.Result属性。

public async Task<string> GetName(int id)
{
     Task<string> nameTask = Task.Factory.StartNew(() => string.Format("Name matching id {0} = Developer", id));
     return await nameTask;
}

这里我正在使用 await Task<T>。如果我认为 await 将释放调用线程,但 Task<T>.Result 会阻塞它,那么我的想法是正确的吗?


2
由于第二段代码没有续行 - 你什么也得不到。在第一段代码中,你只是标记了方法为异步,但你没有等待。 - Royi Namir
要了解更多关于async-await的内容,请查看我的async-await精选文章 - Paulo Morgado
@RoyiNamir,你的说法不正确吗?第一种方法使用async并没有什么好处,因为该方法会阻塞调用线程。然而,第二种方法应该让当前线程暂停而不是阻塞,直到任务被设置为完成状态。 - Matt
@MattWolf - 我同意第二种方法将对任何调用它的内容有益,因为它可以在 Task 完成时将控制权 relinquish 给调用者。 - Don Cheadle
2个回答

107
我认为如果使用await,会释放调用线程,而Task.Result会阻塞它,这个想法是正确的。一般来说,await task; 会"yield"当前线程,task.Result会阻塞当前线程。await 是一种异步等待;Result是一种阻塞等待。还有一个次要的区别:如果任务以错误状态(即带有异常)完成,则await会像原样重新引发该异常,但Result会将异常包装在AggregateException中。 另外,避免使用Task.Factory.StartNew。几乎从不是正确的方法。如果需要在后台线程上执行工作,请优先考虑使用Task.Run。如果你正在进行动态任务并行,则两者ResultStartNew都是适当的选择;否则应避免使用它们。如果你正在进行异步编程,则两者均不适用。

感谢Stephen Cleary和Yuval Itzchakov为我提供额外的知识。 - Yawar Murtaza
在使用 await Task.WhenAll(task1, task2) 后获取结果和使用 await task1task1.Result 有什么区别吗?等待 WhenAll 应该已经完成了所有线程的工作,对吧? - demo
1
@demo:我更喜欢使用await,原因有两个:1)它避免了AggregateException包装器,2)它对代码更改更宽容 - 即,如果有人更改了方法并且现在任务不再完成。 - Stephen Cleary

6
您是正确的,只要任务没有同步完成。如果它是这样的话,使用 Task.Result 或者 await task 将会同步执行,因为 await 首先会检查任务是否已经完成。否则,如果任务并没有完成,使用 Task.Result 会阻塞调用线程,而使用 await 将会异步等待任务完成。另一个不同之处在于异常处理。前者将传播 AggregationException(其中可能包含一个或多个异常),而后者将解包它并返回基础异常。

顺便提一下,在同步方法上使用异步包装是不好的实践,应该避免。 此外,在异步方法中使用 Task.Result 可能导致死锁,也应该避免。


3
在异步方法中使用 Task.Result 可能会导致死锁,你能详细说明一下这是如何发生死锁的吗? - Deepak
Yuval,你的侧记链接已经失效了。你有其他资源吗?或者你能详细说明一下这个链接在讨论什么吗? - Metro Smurf
1
@MetroSmurf 我修复了链接。这是直接链接:https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/ - Yuval Itzchakov
由于同步上下文的工作方式,当它遇到一个 await 并将控制权返回给调用者时,它会捕获当前上下文。使用 Task.Result 阻塞意味着当回调函数准备好后尝试在该上下文中执行时,它将永远被锁定等待,因为 .Result 会阻塞。关于这种现象,Stack Overflow 上有很多相关问题的解释。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接