让我们考虑一下这个非常简单的异步方法:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
当我用VS2013(前Roslyn编译器)编译它时,生成的状态机是一个结构 .
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
当我使用VS2015(Roslyn)编译它时,生成的代码是这样的:
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
正如您所看到的,Roslyn生成了一个类(而不是结构) . 如果我没记错的话,旧编译器中的async / await支持的第一个实现(CTP2012我猜)也生成了类,然后由于性能原因将其更改为struct . (在某些情况下,你可以完全避免装箱和堆分配......)(见this)
有谁知道为什么罗斯林再次改变了这个? (我对此没有任何问题,我知道这个改变是透明的,不会改变任何代码的行为,我只是好奇)
Edit:
@Damien_The_Unbeliever(以及源代码:))imho的答案解释了一切 . 所描述的Roslyn行为仅适用于调试版本(由于注释中提到的CLR限制,因此需要这样做) . 在Release中它还生成一个结构(具有该...的所有好处) . 所以这似乎是一个非常聪明的解决方案,支持编辑和继续以及更好的 生产环境 性能 . 有趣的东西,感谢所有参与的人!
2 回答
我对此没有任何预知,但由于现在Roslyn是开源的,我们可以通过代码寻找解释 .
在这里,在line 60 of the AsyncRewriter,我们发现:
因此,尽管使用
struct
有一些吸引力,但允许Edit and Continue在async
方法中工作的重大胜利显然被选为更好的选择 .很难为这样的事情给出一个确定的答案(除非编译团队中有人参与:)),但你可以考虑以下几点:
结构的表现“奖金”总是权衡 . 基本上,你得到以下内容:
值语义
可能的堆栈(甚至可能是寄存器?)分配
避免间接
这在等待案件中意味着什么?嗯,实际上......没什么 . 只有非常短的时间段,状态机在堆栈中 - 记住,
await
有效地执行return
,因此方法堆栈会死掉;状态机必须保存在某个地方,并且"somewhere"肯定在堆上 . 堆栈生命周期不适合异步代码:)除此之外,状态机违反了一些定义结构的好指南:
struct
s最多应该是16字节大 - 状态机包含两个指针,它们自己在64位上整齐地填充16字节限制 . 除此之外,还有国家本身,所以它超越了"limit" . 这不是什么大问题,因为它非常适合结构的用例 - 一个基本上是引用类型的结构 .struct
应该是不可变的 - 好吧,这可能不是一个状态机 . 同样,这不是什么大问题,因为结构是自动生成的代码和私有,但是......struct
s应逻辑上表示单个值 . 绝对不是这里的情况,但是从一开始就具有可变状态已经有了类似的结果 .它不应该在任何地方使用泛型 . 状态最终在堆上的某个地方,但至少它只在内部使用,这使得它几乎无效 .
当然,所有这些都是在没有封闭的情况下 . 如果您有遍历
await
的本地(或字段),则状态会进一步膨胀,从而限制了使用结构的有用性 .考虑到所有这些,类方法肯定更清晰,我不希望使用
struct
而言任何明显的性能提升 . 所涉及的所有对象具有相似的生命周期,因此提高内存性能的唯一方法是使它们全部struct
(例如存储在某个缓冲区中) - 当然,这在一般情况下是不可能的 . 大多数情况下,你首先使用await
(也就是说,一些异步I / O工作)已经涉及其他类 - 例如,数据缓冲区,字符串......你不太可能只是返回42
没有做任何堆分配 .最后,我要说的是,你真正看到真正的性能差异的唯一地方就是基准测试 . 对基准进行优化是一个愚蠢的想法,至少可以说......