c# iterator async-await yield-return

c# - ¿Por qué no puedes usar el rendimiento en un lambda, cuando puedes usar esperar en un lambda?



iterator async-await (3)

Según Eric Lippert, los iteradores anónimos no se agregaron al lenguaje porque sería muy complicado implementarlo.

Eso está bien para mí, y no me molestó hasta que siguieron adelante e implementaron métodos asíncronos anónimos. El compilador tiene que hacer lo mismo con los métodos asíncronos que con los iteradores (convertirlos en máquinas de estado), por lo que estoy muy confundido de por qué los iteradores anónimos no están permitidos también, cuando lo son los métodos asíncronos anónimos.

¿Alguien puede arrojar algo de luz sobre esto?


Según Eric Lippert, los iteradores anónimos no se agregaron al lenguaje porque sería muy complicado implementarlo.

Eso no es precisamente lo que pretendía transmitir. El costo relevante es el costo de implementación, sí, pero es el costo de implementación en un compilador existente que no se configuró arquitectónicamente para implementar esa característica compleja .

El compilador tiene que hacer lo mismo con los métodos asíncronos que con los iteradores (convertirlos en máquinas de estado), por lo que estoy muy confundido de por qué los iteradores anónimos no están permitidos también, cuando lo son los métodos asíncronos anónimos.

Una breve historia es relevante. C # primero tuvo métodos anónimos y bloques de iteradores en C # 2.0. Cuando agregué lambdas en C # 3.0, fue un costo importante refactorizar todo el código de método anónimo existente para que pudiera manejar todas las nuevas características de lambdas. Eso lo hizo aún más complicado y costoso de modificar. Hacer iterador bloque lambdas fue juzgado demasiado costoso por los beneficios que se acumularían; Hubiera sido un gran porcentaje del costo total. No podíamos permitírnoslo. Si sumó a cada equipo en el programa de trabajo de la División de Desarrolladores, el equipo con el "polo más largo" fue el equipo del compilador C # 3.0, y mi trabajo en el analizador semántico fue el polo más largo de IIRC en el equipo del compilador. Cada día podríamos haber deslizado C # 3.0, ese habría sido un día que Visual Studio se habría deslizado. Por lo tanto, cualquier cosa que no hiciera que LINQ fuera mejor se cortó, y eso incluía iterador lambdas.

En C # 4, las lambdas de iteradores fueron una característica de muchas de las que se consideraron. Teníamos una lista de buenas características potenciales literalmente más largas que tu brazo y podíamos permitirnos hacer menos de una décima parte de ellas.

En C # 5 el equipo agregó métodos asíncronos. Los equipos de diseño e implementación intentaron durante mucho tiempo crear una abstracción subyacente que fuera común tanto para el bloque de iteradores como para esperar a que se vuelvan a escribir; obviamente son similares, como se nota. Pero en última instancia, el costo de encontrar la solución general no se pagó por sí mismo. La generalidad es sorprendentemente costosa, y encontrar una generalidad que por diseño unifique solo dos cosas es una tontería si no es barato.

Por lo tanto, se tomó la decisión de implementar la reescritura de espera como algo propio. Dado que el equipo iba a asumir este gran costo, y dado que la transformación original de los métodos asíncronos iba a tener una forma lambda de todos modos, se tomó la decisión de invertir en la función completa: métodos asíncronos que contienen lambdas, async lambdas Contiene lambdas, todo el trato. El costo de esa función era una pequeña fracción del costo de toda la función, que era extremadamente costoso.

Y de nuevo, tenemos un problema con los polos largos. Se debe evitar cualquier trabajo en el motor lambda que potencialmente podría haber desestabilizado la await , y eso incluye intentar que funcionen con bloques de iteradores.

Ahora compara Visual Basic. VB durante mucho tiempo no tuvo bloqueos de iteradores en absoluto. Cuando se agregaron, no había infraestructura para seguir trabajando. Todo se pudo construir desde cero para manejar bloques iteradores que contienen lambdas y lambdas que contienen bloques iteradores, y así se hizo.

El compilador de C # ha sido completamente revisado y reescrito a través del proyecto Roslyn. Espero que esto reduzca el costo de la implementación de bloques de iteradores en una versión futura hipotética de C #. ¡Veremos!


Los bloques de iteradores anónimos, aunque son agradables, no tienen un beneficio particularmente convincente. No es un gran factor disuasivo para que los bloques de iteradores se refaculen en su propio método.

async métodos anónimos async tienen mucho más sentido conceptual, no justifican la refactorización en su propio método de la misma manera que lo hacen los bloques de iteradores anónimos, y tienen un beneficio para el usuario final mucho más convincente.

En resumen, los beneficios valían el costo de implementación, a diferencia de los bloques de iteradores. Los costos eran probablemente bastante comparables.


Mira este código (no funciona, solo un ejemplo):

Task<IEnumerable<int>> resultTask = new Task<IEnumerable<int>>(() => { for (int i = 0; i < 10; ++i) { yield return i; } });

¿No te parece una especie de desestructurado?

Asumiendo toda la gama de usos de lambdas, sería muy difícil y no valdría la pena manejar el "laziness" adecuadamente.

Sin embargo, hay grandes enfoques para yield rendimiento de las tareas paralelas .

Pero echemos un vistazo a lo siguiente. Definiendo un método con yield :

static IEnumerable<int> GetIntegers() { for (int i = 0; i < 10; ++i) { yield return i; } }

Y ponerlo en lambda funcionará:

Task<IEnumerable<int>> resultTask = new Task<IEnumerable<int>>(() => { return GetIntegers(); });

¿De qué manera se comportará este código? ¿Va a perder las ventajas del yield real?