¿Por qué son clases de máquinas de estado asíncrono (y no estructuras) en Roslyn?


Consideremos este método asincrónico muy simple:

static async Task myMethodAsync() 
{
    await Task.Delay(500);
}

Cuando compilo esto con VS2013 (compilador pre Roslyn) la máquina de estado generada es una estructura.

private struct <myMethodAsync>d__0 : IAsyncStateMachine
{  
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

Cuando lo compilo con VS2015 (Roslyn) el código generado es este:

private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

Como puede ver Roslyn genera una clase (y no una estructura). Si recuerdo correctamente las primeras implementaciones del soporte async/await en el compilador antiguo (supongo que CTP2012) también generaron clases y luego se cambió a struct de razones de rendimiento. (en algunos casos puede evitar completamente el boxeo y la asignación de montones...) (Ver esto )

¿Alguien sabe por qué esto fue cambiado de nuevo en Roslyn? (No tengo ningún problema con respecto a esto, sé que este cambio es transparente y no cambia el comportamiento de ningún código, solo tengo curiosidad)

Editar:

La respuesta de @Damien_The_Unbeliever (y el código fuente :) ) en mi humilde opinión lo explica todo. El comportamiento descrito de Roslyn solo se aplica para la compilación de depuración (y eso es necesario debido a la limitación de CLR mencionada en el comentario). En Release también genera una estructura (con todos los beneficios de eso..). Así que esto parece ser una solución muy inteligente para apoyar tanto Editar y Continuar y un mejor rendimiento en la producción. Cosas interesantes, gracias por todos los que participaron!

Author: Community, 2015-11-23

2 answers

No tenía ningún conocimiento previo de esto, pero ya que Roslyn es de código abierto en estos días, podemos ir a la caza a través del código para una explicación.

Y aquí, en la línea 60 del AsyncRewriter , encontramos:

// 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;

Así que, aunque hay cierto atractivo para usar structs, la gran victoria de permitir Editar y Continuar para trabajar dentro de los métodos async fue obviamente elegida como la mejor opción.

 105
Author: Damien_The_Unbeliever,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-25 07:21:34

Es difícil dar una respuesta definitiva para algo como esto (a menos que alguien del equipo del compilador aparezca :)), pero hay algunos puntos que puede considerar:

El "bonus" de rendimiento de las estructuras es siempre una compensación. Básicamente, obtienes lo siguiente:

  • Semántica de valores
  • Pila posible(tal vez incluso registro? asignación
  • Evitando indirectas

¿Qué significa esto en el caso await? Bueno, en realidad... Nada. Sólo hay un período de tiempo muy corto durante el cual la máquina de estados está en la pila - recuerde, await efectivamente hace un return, por lo que la pila de métodos muere; la máquina de estados debe conservarse en algún lugar, y ese "en algún lugar" definitivamente está en el montón. La vida útil de la pila no se ajusta bien al código asíncrono:)

Aparte de esto, la máquina de estados viola algunas buenas pautas para definir estructuras:

  • structs debe tener un tamaño máximo de 16 bytes-la máquina de estados contiene dos punteros, que por su cuenta llenan el límite de 16 bytes cuidadosamente en 64 bits. Aparte de eso, está el propio estado, por lo que va más allá del "límite". Esto no es un gran acuerdo, ya que es muy probable que solo se pase por referencia, pero tenga en cuenta que no se ajusta al caso de uso de las estructuras, una estructura que es básicamente un tipo de referencia.
  • struct s debe ser inmutable - bueno, esto probablemente no necesita mucho de un comentario. Es una máquina de estado . Una vez más, esto no es un gran problema, ya que el struct es código auto-generado y privado, pero...
  • struct s debe representar lógicamente un solo valor. Definitivamente no es el caso aquí, pero eso ya se deriva de tener un estado mutable en primer lugar.
  • No se debe encajonar con frecuencia, no es un problema aquí, ya que estamos usando genéricos en todas partes. El estado está en última instancia en algún lugar del montón, pero al menos no está siendo encajonado (automáticamente). Una vez más, el hecho de que solo se utiliza internamente hace que esto casi vacío.

Y por supuesto, todo esto es en un caso donde no hay cierres. Cuando tiene locales (o campos) que atraviesan las awaits, el estado se infla aún más, limitando la utilidad de usar una estructura.

Dado todo esto, el enfoque de clase es definitivamente más limpio, y no esperaría ningún aumento de rendimiento notable al usar un struct en su lugar. Todos los objetos involucrados tienen una vida útil similar, por lo que la única manera de mejorar el rendimiento de la memoria sería hacer todos de ellos struct s (almacenar en algún búfer, por ejemplo) - lo cual es imposible en el caso general, por supuesto. Y la mayoría de los casos en los que usarías await en primer lugar (es decir, algún trabajo de E/S asincrónico) ya involucran otras clases, por ejemplo, búferes de datos, cadenas... Es bastante poco probable que await algo que simplemente devuelve 42 sin hacer ninguna asignación de montón.

Al final, yo diría que el único lugar donde realmente verías una actuación real la diferencia sería puntos de referencia. Y optimizar para los puntos de referencia es una idea tonta, por decir lo menos...

 3
Author: Luaan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-23 13:15:45