首页 文章

为什么Roslyn中的异步状态机类(而不是结构)?

提问于
浏览
82

让我们考虑一下这个非常简单的异步方法:

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 回答

  • 105

    我对此没有任何预知,但由于现在Roslyn是开源的,我们可以通过代码寻找解释 .

    在这里,在line 60 of the AsyncRewriter,我们发现:

    // The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
    var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;
    

    因此,尽管使用 struct 有一些吸引力,但允许Edit and Continueasync 方法中工作的重大胜利显然被选为更好的选择 .

  • 3

    很难为这样的事情给出一个确定的答案(除非编译团队中有人参与:)),但你可以考虑以下几点:

    结构的表现“奖金”总是权衡 . 基本上,你得到以下内容:

    • 值语义

    • 可能的堆栈(甚至可能是寄存器?)分配

    • 避免间接

    这在等待案件中意味着什么?嗯,实际上......没什么 . 只有非常短的时间段,状态机在堆栈中 - 记住, await 有效地执行 return ,因此方法堆栈会死掉;状态机必须保存在某个地方,并且"somewhere"肯定在堆上 . 堆栈生命周期不适合异步代码:)

    除此之外,状态机违反了一些定义结构的好指南:

    • struct s最多应该是16字节大 - 状态机包含两个指针,它们自己在64位上整齐地填充16字节限制 . 除此之外,还有国家本身,所以它超越了"limit" . 这不是什么大问题,因为它非常适合结构的用例 - 一个基本上是引用类型的结构 .

    • struct 应该是不可变的 - 好吧,这可能不是一个状态机 . 同样,这不是什么大问题,因为结构是自动生成的代码和私有,但是......

    • struct s应逻辑上表示单个值 . 绝对不是这里的情况,但是从一开始就具有可变状态已经有了类似的结果 .

    • 它不应该在任何地方使用泛型 . 状态最终在堆上的某个地方,但至少它只在内部使用,这使得它几乎无效 .

    当然,所有这些都是在没有封闭的情况下 . 如果您有遍历 await 的本地(或字段),则状态会进一步膨胀,从而限制了使用结构的有用性 .

    考虑到所有这些,类方法肯定更清晰,我不希望使用 struct 而言任何明显的性能提升 . 所涉及的所有对象具有相似的生命周期,因此提高内存性能的唯一方法是使它们全部 struct (例如存储在某个缓冲区中) - 当然,这在一般情况下是不可能的 . 大多数情况下,你首先使用 await (也就是说,一些异步I / O工作)已经涉及其他类 - 例如,数据缓冲区,字符串......你不太可能只是返回 42 没有做任何堆分配 .

    最后,我要说的是,你真正看到真正的性能差异的唯一地方就是基准测试 . 对基准进行优化是一个愚蠢的想法,至少可以说......

相关问题