¿Es Async await la palabra clave equivalente a ContinueWith lambda?

81

¿Podría alguien tener la amabilidad de confirmar si he entendido correctamente la palabra clave Async await? (Usando la versión 3 del CTP)

Hasta ahora, he descubierto que insertar la palabra clave await antes de una llamada a un método esencialmente hace 2 cosas, A. Crea un retorno inmediato y B. Crea una "continuación" que se invoca al completar la invocación del método asincrónico. En cualquier caso, la continuación es el resto del bloque de código del método.

Entonces, lo que me pregunto es si estos dos bits de código son técnicamente equivalentes y, de ser así, ¿esto básicamente significa que la palabra clave await es idéntica a la creación de un ContinueWith Lambda (es decir, es básicamente un acceso directo del compilador para uno)? Si no es así, ¿cuáles son las diferencias?

bool Success =
    await new POP3Connector(
        "mail.server.com", txtUsername.Text, txtPassword.Text).Connect();
// At this point the method will return and following code will
// only be invoked when the operation is complete(?)
MessageBox.Show(Success ? "Logged In" : "Wrong password");

VS

(new POP3Connector(
    "mail.server.com", txtUsername.Text, txtPassword.Text ).Connect())
.ContinueWith((success) =>
    MessageBox.Show(success.Result ? "Logged In" : "Wrong password"));
Maxim Gershkovich
fuente

Respuestas:

83

La idea general es correcta: el resto del método se convierte en una especie de continuación.

La publicación del blog "ruta rápida" contiene detalles sobre cómo el async/await transformación compilador.

Diferencias, fuera de mi cabeza:

La awaitpalabra clave también hace uso de un concepto de "contexto de programación". El contexto de programación es, SynchronizationContext.Currentsi existe, retroceder TaskScheduler.Current. Luego, la continuación se ejecuta en el contexto de programación. Entonces, una aproximación más cercana sería pasar TaskScheduler.FromCurrentSynchronizationContexta ContinueWith, recurriendo a TaskScheduler.Currentsi es necesario.

El actual async/ awaitaplicación se basa en la coincidencia de patrones; utiliza un patrón "de espera" que permite esperar otras cosas además de las tareas. Algunos ejemplos son las API asíncronas de WinRT, algunos métodos especiales como Yield, observables Rx y esperables de sockets especiales que no golpean el GC con tanta fuerza . Las tareas son poderosas, pero no son las únicas pendientes.

Me viene a la mente una pequeña diferencia más delicada: si el awaitable ya está completo, entonces el asyncmétodo no regresa en ese momento; continúa sincrónicamente. Es como pasar TaskContinuationOptions.ExecuteSynchronously, pero sin los problemas relacionados con la pila.

Stephen Cleary
fuente
2
muy bien dicho: trato de cederme a las publicaciones de Jon, ya que son mucho más extensas que cualquier cosa que tuviera tiempo de poner en una respuesta sobre SO, pero Stephen tiene toda la razón. WRT lo que está a la espera (y GetAwaiter en particular), su publicación # 3 es muy útil en mi humilde opinión :) msmvps.com/blogs/jon_skeet/archive/2011/05/13/…
James Manning
4
El lugar de Stephen aquí. Para ejemplos simples, es fácil pensar que async / await es solo un atajo para ContinueWith; sin embargo, me gusta pensar en ello al revés. Async / await es en realidad una expresión más poderosa de lo que solía usar ContinueWith. El problema es que ContinueWith (...) usa lambdas y permite que la ejecución se transfiera a la continuación, pero otros conceptos de flujo de control, como los bucles, son bastante imposibles si tiene que poner la mitad del cuerpo del bucle antes de ContinueWith (.. .) y la otra mitad después. Termina con el encadenamiento de continuación manual.
Theo Yaung
7
Otro ejemplo en el que async / await es mucho más expresivo que ContinueWith (...) es un flujo de excepciones. Puede esperar varias veces dentro del mismo bloque try, y para cada etapa de ejecución, sus excepciones se pueden canalizar al mismo bloque catch (...) sin tener que escribir toneladas de código que lo hagan explícitamente.
Theo Yaung
2
La última parte de async / await que es notable es que es un "concepto de nivel superior", mientras que ContinueWith (...) es más manual y explícitamente tiene lambdas, creaciones delegadas, etc. Con conceptos de nivel superior hay más oportunidades de optimización, por lo que para Por ejemplo, varias esperas en el mismo método en realidad "comparten" el mismo cierre lambda (es como la sobrecarga de una sola lambda), mientras que ContinueWith (...) obtiene la sobrecarga cada vez que lo invoca, porque escribió explícitamente una lambda, entonces el compilador te lo da.
Theo Yaung
1
@MobyDisk: Para aclarar, awaittodavía captura SynchronizationContext.Currentcomo siempre lo ha hecho. Pero en ASP.NET Core, sí lo SynchronizationContext.Currentes null.
Stephen Cleary
8

Es "esencialmente" eso, pero el código generado hace estrictamente más que eso. Para obtener más detalles sobre el código generado, recomiendo encarecidamente la serie Eduasync de Jon Skeet:

http://codeblog.jonskeet.uk/category/eduasync/

En particular, la publicación n. ° 7 aborda lo que se genera (a partir de CTP 2) y por qué, por lo que probablemente sea una excelente opción para lo que está buscando en este momento:

http://codeblog.jonskeet.uk/2011/05/20/eduasync-part-7-generated-code-from-a-simple-async-method/

EDITAR: Creo que es probable que haya más detalles de lo que está buscando en la pregunta, pero si se pregunta cómo se ven las cosas cuando tiene múltiples esperas en el método, eso se cubre en la publicación # 9 :)

http://codeblog.jonskeet.uk/2011/05/30/eduasync-part-9-generated-code-for-multiple-awaits/

James Manning
fuente