Borrando las líneas entre las funciones asíncronas y regulares en C # 5.0

10

Últimamente parece que no puedo tener suficiente del sorprendente patrón de espera asíncrona de C # 5.0. Donde has estado toda mi vida?

Estoy absolutamente encantado con la sintaxis simple, pero tengo una pequeña dificultad. Mi problema es que las funciones asíncronas tienen una declaración totalmente diferente de las funciones normales. Dado que solo las funciones asíncronas pueden esperar en otras funciones asíncronas, cuando intento portar algún código de bloqueo antiguo a asíncrono, tengo un efecto dominó de las funciones que tengo que convertir.

La gente se ha referido a esto como una infestación de zombis . Cuando async obtiene una mordida en su código, seguirá creciendo más y más. El proceso de portabilidad no es difícil, solo incluye asyncla declaración y ajusta el valor de retorno Task<>. Pero es molesto hacer esto una y otra vez al portar código síncrono antiguo.

Me parece que será mucho más natural si ambos tipos de funciones (asíncrona y sincronización antigua simple) tuvieran exactamente la misma sintaxis. Si este fuera el caso, la transferencia no requerirá esfuerzo y podría cambiar sin problemas entre las dos formas.

Creo que esto podría funcionar si seguimos estas reglas:

  1. Las funciones asincrónicas ya no requerirán la asyncdeclaración. Sus tipos de devolución no tendrían que estar envueltos Task<>. El compilador identificará una función asíncrona durante la compilación por sí mismo y realizará la tarea <> ajustando automáticamente según sea necesario.

  2. No más llamadas de disparar y olvidar a funciones asíncronas. Si desea llamar a una función asíncrona, deberá esperarla. De todos modos, casi nunca utilizo disparar y olvidar, y todos los ejemplos de condiciones de carrera locas o puntos muertos siempre parecen estar basados ​​en ellos. Creo que son demasiado confusos y "fuera de contacto" con la mentalidad sincrónica que tratamos de aprovechar.

  3. Si realmente no puede vivir sin disparar y olvidar, habrá una sintaxis especial para ello. En cualquier caso, no será parte de la sintaxis unificada simple de la que estoy hablando.

  4. La única palabra clave que necesita para denotar una llamada asincrónica es await. Si ha esperado, la llamada es asíncrona. Si no lo hace, la llamada es sincrónica antigua (recuerde, ya no tenemos disparar y olvidar).

  5. El compilador identificará las funciones asíncronas automáticamente (ya que ya no tienen una declaración especial). La regla 4 hace que esto sea muy simple: si una función tiene una awaitllamada dentro, es asíncrona.

¿Esto podría funcionar? ¿O me estoy perdiendo algo? Esta sintaxis unificada es mucho más fluida y podría resolver la infestación de zombis por completo.

Algunos ejemplos:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

Nota: esta es una continuación de una discusión relacionada interesante en SO

talkol
fuente
3
¿Siempre espera en sus operaciones asincrónicas? Por favor, dime que no haces esto inmediatamente después de dispararlos ...
Jimmy Hoffa
1
Además, una de las mejores cosas de async es que no tiene que hacerlo de awaitinmediato. Se puede hacer algo así var task = FooAsync(); Bar(); await task;. ¿Cómo haría esto en tu propuesta?
svick
3
¿Entonces está teniendo una discusión? ¿Dónde está mi BFG-3000 ...
Robert Harvey
2
@talkol ¿Crees que la programación paralela es obscena? Esa es una perspectiva interesante, por decir lo menos, cuando estás hablando async. Creo que esa es una de las grandes ventajas de async- await: que le permite componer fácilmente las operaciones asincrónicas (y no sólo en el más simple "iniciar una, espera por A, B empezar, espera para B" camino). Y ya existe una sintaxis especial exactamente para este propósito: se llama await.
svick
1
@svick jaja, ahora hemos ido allí :) No creo que el programa paralelo sea obsceno, pero creo que hacerlo con async-waitit lo es. Async-await es azúcar sintáctica para mantener su estado mental sincrónico sin pagar el precio del bloqueo. Si ya está pensando en paralelo, le recomiendo que use un patrón diferente
talkol

Respuestas:

9

Su pregunta ya está respondida en la pregunta SO que ha vinculado.

El propósito de async / await es facilitar la escritura de código en un mundo con muchas operaciones de alta latencia. La gran mayoría de sus operaciones no son de alta latencia.

Cuando salió por primera vez WinRT , los diseñadores describían cómo decidieron qué operaciones iban a ser asíncronas. Decidieron que cualquier cosa que tomara 50 ms o más sería asíncrona, y el resto de los métodos serían métodos ordinarios, no asíncronos.

¿Cuántos de los métodos tuvieron que ser reescritos para hacerlos asíncronos? Alrededor del 10 por ciento de ellos. El otro 90% no se vio afectado en absoluto.

Eric Lippert continúa explicando con detalles técnicos bastante sustanciales por qué optaron por no adoptar un enfoque único para todos. Básicamente dice eso asyncy awaites una implementación parcial del estilo de paso continuo, y que optimizar ese estilo para que se adapte a todos los casos es un problema difícil.

Robert Harvey
fuente
Tenga en cuenta la diferencia sustancial entre la pregunta SO y esta. El SO pregunta por qué no hacer todo asíncrono. Aquí no sugerimos eso, sugerimos hacer un 10% asíncrono, solo usar la misma sintaxis para eso es todo. El uso de una sintaxis más cercana tiene la ventaja de que puede cambiar más fácilmente el 10% que es asíncrono, sin sufrir los efectos de dominó de estos cambios
talkol
No estoy claro por qué asyncproduciría infestaciones de zombis. Incluso si un método llama a otros 10 métodos, ¿no tiene que pasar al asyncnivel superior?
Robert Harvey
66
Digamos que el 100% de mi código actual es sincronización. Ahora tengo una única función interna de nivel de hoja que consulta la base de datos que me gustaría cambiar a asíncrona. Ahora para hacerlo asíncrono, necesito que la persona que llama sea asíncrona, y que la persona que llama sea asíncrona, y así sucesivamente hasta el nivel superior. Por supuesto, estoy hablando del caso en el que se espera toda la cadena (para mantener el diseño síncrono del código o pasar valles de retorno)
talkol