Consideremos este método asincrónico muy simple:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
Cuando compilo esto con VS2013 (compilador anterior a 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 (CTP2012 supongo) también generaron clases y luego se cambió a estructura por razones de rendimiento. (en algunos casos puede evitar por completo el boxeo y la asignación de pila…) (Vea esto )
¿Alguien sabe por qué se volvió a cambiar esto en Roslyn? (No tengo ningún problema al respecto, 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 a la compilación de depuración (y eso es necesario debido a la limitación CLR mencionada en el comentario). En Release también genera una estructura (con todos los beneficios de eso ..). Así que esta parece ser una solución muy inteligente para admitir tanto Editar como Continuar y mejorar el rendimiento en producción. Cosas interesantes, ¡gracias a todos los que participaron!
fuente
async
Los métodos casi siempre tienen un verdadero punto asincrónico,await
que produce control, lo que requeriría que la estructura esté enmarcada de todos modos. Creo que las estructuras solo aliviarían la presión de la memoria para losasync
métodos que se ejecutaron sincrónicamente.Respuestas:
No tenía ningún conocimiento previo de esto, pero como Roslyn es de código abierto en estos días, podemos buscar una explicación en el código.
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;
Entonces, aunque hay algo de atractivo en usar
struct
s, la gran ventaja de permitir que Editar y Continuar funcione dentro de losasync
métodos obviamente se eligió como la mejor opción.fuente
Es difícil dar una respuesta definitiva para algo como esto (a menos que alguien del equipo del compilador ingrese :)), pero hay algunos puntos que puede considerar:
La "bonificación" de rendimiento de las estructuras es siempre una compensación. Básicamente, obtienes lo siguiente:
¿Qué significa esto en el caso de espera? Bueno, en realidad ... nada. Solo hay un período de tiempo muy corto durante el cual la máquina de estado está en la pila; recuerde,
await
efectivamente hace areturn
, por lo que la pila de métodos muere; la máquina de estado debe conservarse en algún lugar, y ese "lugar" definitivamente está en el montón. La vida útil de la pila no se ajusta bien al código asincrónico :)Aparte de esto, la máquina de estados viola algunas buenas pautas para definir estructuras:
struct
s debe tener un tamaño máximo de 16 bytes: la máquina de estado contiene dos punteros, que por sí solos llenan el límite de 16 bytes de forma ordenada en 64 bits. Aparte de eso, está el estado mismo, por lo que supera el "límite". Esto no es un gran problema, ya que es muy probable que solo se pase por referencia, pero tenga en cuenta que eso 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 necesite mucho comentario. Es una máquina de estado . Nuevamente, esto no es un gran problema, ya que la estructura es un código generado automáticamente y es privado, pero ...struct
s debe representar lógicamente un valor único. Definitivamente no es el caso aquí, pero eso ya se deriva de tener un estado mutable en primer lugar.Y, por supuesto, todo esto es en un caso donde no hay cierres. Cuando tiene locales (o campos) que atraviesan la
await
s, el estado se infla aún más, lo que limita 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 notable del rendimiento al usar un
struct
. Todos los objetos involucrados tienen una vida útil similar, por lo que la única forma de mejorar el rendimiento de la memoria sería hacer que todos ellos fueranstruct
s (almacenarlos 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íaawait
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 hagaawait
algo que simplemente regrese42
sin hacer nada asignaciones de montón.Al final, diría que el único lugar donde realmente vería una diferencia de rendimiento real serían los puntos de referencia. Y optimizar para los puntos de referencia es una idea tonta, por decir lo menos ...
fuente