async-await的模式.NET4.5正在改变范式.这太好了,简直难以置信.

我一直在向async await移植一些IO密集型代码,因为阻塞已经成为过go .

相当多的人将异步等待比作僵尸出没,我发现这是相当准确的.异步代码与其他异步代码类似(您需要异步函数才能等待异步函数).因此,越来越多的函数成为异步的,这在您的代码库中不断增长.

将功能更改为异步是有点重复且缺乏想象力的工作.在声明中抛出一个async关键字,用Task<>包装返回值,就差不多完成了.整个过程非常容易,很快就会有一个文本替换脚本为我自动完成大部分"移植"工作.

现在问题是...如果我的所有代码都在慢慢变成异步,为什么不在默认情况下将其全部设为异步呢?

我认为最明显的原因是性能.Async await的开销和代码不需要是异步的,最好是不应该是异步的.但是如果性能是唯一的问题,当然一些巧妙的优化可以在不需要时自动消除开销.我读过关于"fast path"优化的书,在我看来,单靠它就可以解决大部分问题.

也许这可以与垃圾收集器带来的范式转变相提并论.在早期的GC时代,释放您自己的内存肯定更有效率.但是大众仍然 Select 自动收集,而偏爱可能效率较低的更安全、更简单的代码(甚至可以说,现在已经不是这样了).也许这里应该是这样?为什么不是所有功能都应该是异步的呢?

推荐答案

首先,谢谢你的客气话.这确实是一个很棒的功能,我很高兴成为它的一小部分.

如果我所有的代码都在慢慢变为异步,为什么不在默认情况下使其全部异步呢?

嗯,你夸大其词了;all你的代码不是异步的.当您将两个"纯"整数相加时,您并不是在等待结果.当您将两个future integers相加得到第三个future integer时--因为这就是Task<int>,它是一个整数,您将在将来访问它--当然,您可能会等待结果.

不将所有内容设置为异步的主要原因是the purpose of async/await is to make it easier to write code in a world with many high latency operations.您的绝大多数操作都是not高延迟,因此采取降低延迟的性能损失是没有任何意义的.相反,您的key few%的操作都是高延迟的,而这些操作正在导致整个代码中异步的僵尸出没.

如果性能是唯一的问题,那么一些巧妙的优化当然可以在不需要时自动消除开销.

在理论上,理论和实践是相似的.实际上,它们从来都不是.

让我给你三点建议,反对这种先进行优化再进行转换的做法.

第一点是:C#/VB/F#中的异步本质上是continuation passing的有限形式.函数式语言社区中的大量研究已经找到了确定如何优化大量使用延续传递风格的代码的方法.在"异步"是默认值,非异步方法必须被识别和反异步化的情况下,编译器团队可能必须解决非常类似的问题.C#团队对解决开放性研究问题并不感兴趣,所以这是他们面临的主要问题.

反对的第二点是C#没有使这类优化更容易处理的"引用透明性"级别.我所说的"参照透明性"是指the value of an expression does not depend on when it is evaluated.像2 + 2这样的表达式在引用上是透明的;如果需要,您可以在编译时进行求值,或者将其推迟到运行时再得到相同的结果.但是像x+y这样的表达式不能在时间上移动,因为x and y might be changing over time.

Async使人们更难推断副作用何时会发生.在异步之前,如果您说:

M();
N();

M()void M() { Q(); R(); },N()void N() { S(); T(); },RS产生副作用,那么你就知道R的副作用发生在S的副作用之前.但是如果你有async void M() { await Q(); R(); }个,那么它突然就被抛到了窗外.您不能保证R()会发生在S()之前还是之后(当然,除非等待M();但当然,Task不需要等到N()之后).

现在假设这个属性no longer knowing what order side effects happen in适用于every piece of code in your program,除了优化器设法go 异步的那些.基本上你已经不知道哪些表达式将按什么顺序计算,这意味着所有表达式都需要是引用透明的,这在C#这样的语言中很难做到.

反对的第三点是,然后必须问"为什么异步如此特殊?"如果你认为每一个操作都应该是Task<T>,那么你需要能够回答"为什么不是Lazy<T>?"或者"为什么不呢?"或者"为什么不呢?"因为我们可以很容易做到.为什么不应该是every operation is lifted to nullable呢?或者every operation is lazily computed and the result is cached for later,或者the result of every operation is a sequence of values instead of just a single value.然后,您必须try 优化那些您知道"哦,这永远不能为null,这样我才能生成更好的代码"的情况,等等.(事实上,C#编译器对提升算法也是如此.)

重点是:我不清楚Task<T>真的有那么特别,能保证这么多的工作.

如果您对这类事情感兴趣,那么我建议您研究像Haskell这样的函数式语言,它们具有更强的引用透明性,并且允许所有类型的无序计算和自动缓存.Haskell在其类型系统中对我提到的那种"一元式提升"也有更强的支持.

.net相关问答推荐

ASP.NET核心最小API必须以正斜杠开头吗?

dotnet 8 web api在部署到docker后无法工作

.NET模拟具有泛型返回类型的方法

如何通过在后台预加载流项来优化流迭代的性能?

您可以为 Func 或 Action 类型的函数或子参数指定默认值吗?

在 WP7 中将 List 转换为 ObservableCollection

如何在 MSBuild 脚本中获取当前目录?

Style 和 ControlTemplate 的区别

共享 AssemblyInfo 用于跨解决方案的统一版本控制

SubscribeOn 和 ObserveOn 有什么区别

什么是编组?当某些东西被编组时会发生什么?

参数命名:文件名还是文件名?

从 .NET 中的字符串末尾修剪字符串 - 为什么会丢失?

如何在多个解决方案之间共享相同的 Resharper 设置,无需人工干预?

Environment.GetFolderPath(...CommonApplicationData) 在 Vista 上仍然返回C:\Documents and Settings\

ConcurrentDictionary TryRemove 何时返回 false

找不到库 hostpolicy.dll

如何在 nuspec 中指定特定的依赖版本?

功能说明

如何获取当前的 ProcessID?