一个新库中异步和非异步方法的区别

9
在.NET 4.5中,许多方法现在都有异步和非异步对应的版本,例如Flush()FlushAsync()。理想情况下,I/O交互应该始终尽可能地异步(如果您真的想要,可以使用.Wait()阻塞),但由于向后兼容性,非异步(阻塞)版本显然需要保留。
当推出一个完全没有向后兼容限制的新库时,是否有任何理由包括非异步方法?

1
我认为这有点主观(因为如果你只是在实现中使用wait来重载它们,那么就无法获得任何好处),但我会包括它们 - 如果用户不想要异步操作,就没有必要强制他进行异步操作(无论他的原因是好是坏)- 但这只是我的意见(node.js的人会有一个完全不同的意见)- 这就是为什么你的问题可能很快就会被关闭 - 没有好的答案。 - Random Dev
我认为这不仅仅是一个观点问题……我相信肯定有一些指导方针或最佳实践。但再想一想,最佳实践只是一种观点,所以也许我们不应该在Stack Overflow上寻求最佳实践? - Gigi
@Gigi 或许你应该在 Meta 上问一下 ;) - 我只是想给你一个提示,为什么你现在有两个关闭标志 - 并不是为了争论。 - Random Dev
@CarstenKönig 当然 - 非常感谢。我发布这个问题的时候就意识到了被关闭的风险,因为 SO 社区非常热衷于关闭问题,但是我更愿意让人们贡献知识并关闭我的问题,而不是一无所获。 - Gigi
2
@CarstenKönig 这个问题确实可能被认为是“主观的”,但我认为这个问题的价值完全超过了这一点 - 这正是人们在谷歌搜索“async vs non-async”等时想要找到的东西。 - Luaan
4个回答

7
异步方法通常会带来一些成本,因为编译器会生成一个状态机,导致额外的代码量。如果您不使用 async 方法,它们就不会被编译,从而避免了这种成本。
如果您使用 async 版本并简单地调用 Wait(),那么您会冒着 死锁 的风险,并且在 async 操作完成后会产生额外的上下文切换。因此,总体而言,结果的性能会稍微差一些。
此外,您获得的任何异常现在都将被包装在 AggregatedException 中,因此在异常处理方面还需要额外的工作。请参阅 异步性能:了解异步和等待的成本

2
这是一个很好的观点,但是你应该记住,所有这些成本几乎总是与底层I/O请求相比微不足道。因此,使用异步I/O是可以的,几乎任何时候都可以(毕竟,当执行例如File.ReadAllBytes时,它只是在更低的层次上发生)。对于CPU工作等使用它会更昂贵,但是,确实有一个指南反对使XXXAsync方法仅在Task.Run中包装同步代码,因此,如果您正在进行适当的异步工作,则成本并不那么高。 - Luaan
实际上这很有趣。但如果非异步方法不能简单地包装异步方法,那么如何在不重复代码的情况下同时实现它们? - Gigi
@Gigi 嗯,这不一定是代码重复,但是你不会“重用”代码。如果你只是在异步方法上执行了Wait(或者在同步方法上执行了Task.Run,天哪:D),那么这个方法就没有意义了。看看.NET参考源代码,你可以看到它如何与例如FileStream(具有一些复杂的异步/同步方法)或Socket(非常简单直接)一起工作。 - Luaan

4
Ned的答案已经很好了,所以我不会重复他所说的(尽管请注意,开销并不一定大于IO操作本身的成本)。
提供同步方法的一个更重要的原因是显而易见的 - 人们很难理解异步操作。他们习惯了“先读取,然后写入,再次读取...”错误处理、错误局部性、同步...所有这些都很容易变得非常棘手,特别是如果你不能使用await。
事实上,虽然随着时间的推移,如果一切都变为异步的话,那将会是非常好的,但是对于异步应用程序来说,很难进行推理。它仍然比多线程好,但不是太多。
从这个角度看,使用异步方法可以看作是一种优化——只有在你确信它们将带来显著的帮助时才进行优化。对于一个库来说,这是一个很难决定的问题——所以提供两个选项。编写高吞吐服务器的人将采取必要的措施,编写一个高性能和可靠的异步应用程序。而一些简单的CMS的开发者可能会完全避免使用异步方法。
这特别适用于C#/.NET 4.5以下版本——尝试在没有等待的情况下处理异步连续性的错误处理,你就会知道我在说什么:)
保持代码简洁非常重要。它大大降低了开发和维护成本,并使新手更容易理解。

4
async和同步版本之间存在轻微差异(大多可以忽略不计),但我认为同一个操作不再需要两个版本。在实现库的过程中,您应该决定哪个版本最适合您。自然异步操作(例如下载文件)应该是async,而CPU绑定操作应该是同步的。
您可以看到WinRT框架中使用的范例:
为了实现这些目标,我们在Windows运行时中将许多潜在的I/O绑定API设置为异步。这些API是最有可能如果同步编写会显著降低性能的候选项(例如执行时间可能超过50毫秒)。此异步API的方法使您可以编写默认情况下快速流畅的代码,并促进了Metro样式应用程序开发中应用程序响应性的重要性。
引自使用异步保持应用程序快速流畅的Windows运行时中的技巧 以前,使用异步重载比使用同步重载难度和复杂度都要大得多,因此拥有两个版本是一个好主意。但是,由于有了async-await,这种情况不再成立。

0
唯一的原因是如果您想支持针对 .NET Framework 4.5 之前版本的应用程序。如果您有这样的应用程序,并且想要使用新库,则必须使用非异步方式。
您也可以使用 Async Targeting Package 将新程序集编译为 .NET 4.0。我建议您创建两个项目:一个使用 4.5,另一个使用 4.0。

实际上,即使没有 ATP,4.0 版本也可以正常使用,只是无法使用 await。这当然意味着很难做到正确,但我已经被迫多次这样做了,而且它确实有效。当然,如果你的库中有 await,那么你的消费者也必须是 4/4.5 版本,因此编写非异步版本也没有帮助。 - Luaan

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