síncrono sincrono sincrona qué que programacion lenguaje ejemplos asíncrono asincrono asincronia asincrona f# asynchronous

f# - sincrona - que es un lenguaje sincrono



Comprender la programación asíncrona F# (4)

Conozco la sintaxis de la programación asincrónica en F #. P.ej

let downloadUrl(url:string) = async { let req = HttpWebRequest.Create(url) // Run operation asynchronously let! resp = req.AsyncGetResponse() let stream = resp.GetResponseStream() // Dispose ''StreamReader'' when completed use reader = new StreamReader(stream) // Run asynchronously and then return the result return! reader.AsyncReadToEnd() }

En el libro de expertos F # (y muchas otras fuentes), dicen como

¡dejar! var = expr simplemente significa "realizar la operación asincrónica expr y vincular el resultado a var cuando la operación se completa. Luego continúe ejecutando el resto del cuerpo de cálculo"

También sé que se crea un nuevo hilo cuando se realiza una operación asincrónica. Mi comprensión original fue que hay dos hilos paralelos después de la operación asincrónica, uno que hace E / S y otro que continúa ejecutando el cuerpo asincrónico al mismo tiempo.

Pero en este ejemplo, estoy confundido en

let! resp = req.AsyncGetResponse() let stream = resp.GetResponseStream()

¿Qué sucede si resp no ha comenzado todavía y el hilo en el cuerpo asíncrono quiere GetResponseStream ? ¿Es esto un posible error?

Entonces tal vez mi comprensión original fue incorrecta. Las frases citadas en el libro de expertos de F # en realidad significan que "crear un nuevo hilo, colgar el hilo actual, cuando termine el nuevo hilo, activar el hilo del cuerpo y continuar", pero en este caso no veo que podamos guardar en cualquier momento.

En la comprensión original, el tiempo se guarda cuando hay varias operaciones de E / S independientes en un bloque asíncrono para que puedan realizarse al mismo tiempo sin intervención entre sí. Pero aquí, si no obtengo la respuesta, no puedo crear la transmisión; solo tengo transmisión, puedo comenzar a leer la transmisión. ¿Dónde está el tiempo ganado?


Creo que lo más importante para entender sobre los flujos de trabajo asincrónicos es que son secuenciales de la misma manera que el código ordinario escrito en F # (o C #, para el caso) es secuencial. Tienes algunas vinculaciones que se evalúan en el orden habitual y algunas expresiones (que pueden tener efectos secundarios). De hecho, los flujos de trabajo asincrónicos a menudo se parecen más al código imperativo.

El segundo aspecto importante de los flujos de trabajo asincrónicos es que no son de bloqueo . Esto significa que puede tener operaciones que se ejecutan de forma no estándar y no bloquear el hilo durante la ejecución. (En general, las expresiones de cálculo de let! In F # siempre indican que existe un comportamiento no estándar ; puede haber posibilidad de error sin producir resultado en la mónada Maybe , o puede ser una ejecución sin bloqueo para flujos de trabajo asíncronos).

Técnicamente hablando, la ejecución sin bloqueo se implementa registrando alguna devolución de llamada que se activará cuando finalice la operación. Un ejemplo relativamente simple es un flujo de trabajo asíncrono que espera un tiempo específico; esto puede implementarse usando el Timer sin bloquear ningún subproceso (Ejemplo del capítulo 13 de mi libro, la fuente está disponible aquí ):

// Primitive that delays the workflow let Sleep(time) = // ''FromContinuations'' is the basic primitive for creating workflows Async.FromContinuations(fun (cont, econt, ccont) -> // This code is called when workflow (this operation) is executed let tmr = new System.Timers.Timer(time, AutoReset=false) tmr.Elapsed.Add(fun _ -> // Run the rest of the computation cont()) tmr.Start() )

También hay varias formas de utilizar flujos de trabajo asíncronos F # para programación simultánea o paralela, sin embargo, estos son simplemente usos más sofisticados de flujos de trabajo o bibliotecas F # construidos encima de ellos: aprovechan el comportamiento de no bloqueo descrito anteriormente.

  • Puede utilizar StartChild para iniciar un flujo de trabajo en segundo plano: el método le proporciona un flujo de trabajo en ejecución que puede usar (¡usando let! ) Más adelante en el flujo de trabajo para esperar su finalización, mientras puede continuar haciendo otras cosas. Esto es similar a las Tareas en .NET 4.0, pero se ejecuta de forma asíncrona, por lo que es más adecuado para las operaciones de E / S.

  • Puede usar Async.Parallel para crear múltiples flujos de trabajo y esperar hasta que todos se completen (lo cual es ideal para operaciones de datos en paralelo). Esto es similar a PLINQ, pero una vez más, la async es mejor si realiza algunas operaciones de E / S.

  • Finalmente, puede usar MailboxProcessor que le permite escribir aplicaciones concurrentes usando el estilo de paso de mensajes (estilo Erlang). Esta es una gran alternativa a los hilos para muchos problemas.


Esta es una buena pregunta. Es importante tener en cuenta que las sentencias múltiples en un bloque async no se ejecutan en paralelo. async bloques async esencialmente producen tiempo de procesador para otros procesos, mientras que las solicitudes asíncronas están pendientes. Por lo tanto, un bloque async generalmente no se ejecutará más rápido que una secuencia equivalente de operaciones síncronas, pero permitirá que ocurra más trabajo en general. Si está buscando ejecutar varias declaraciones en paralelo, sería mejor que consulte la Biblioteca de tareas paralelas.


La "asincronización" en este ejemplo no se trata de simultaneidad o tiempo de ahorro, sino que se trata de proporcionar un buen modelo de programación sin hilos de bloqueo (leer: desperdicio).

Si usa otros lenguajes de programación, normalmente tiene dos opciones:

Puede bloquear , normalmente llamando a métodos sincrónicos. La desventaja es que el hilo se consume y no realiza ningún trabajo útil mientras espera el disco o la E / S de red o lo que sea. La ventaja es el código simple (código normal).

Puede usar devoluciones de llamada para llamar de forma asíncrona y recibir notificaciones cuando se completen las operaciones. La ventaja es que no se bloquean los hilos (estos hilos se pueden devolver, por ejemplo, a ThreadPool y se usará un nuevo hilo ThreadPool cuando la operación finalice para devolverte la llamada). La desventaja es que un simple bloque de código se divide en un conjunto de métodos de devolución de llamada o lambdas, y rápidamente se vuelve muy complicado mantener el estado / flujo de control / manejo de excepciones en las devoluciones de llamada.

Entonces estás entre una roca y un lugar difícil; o abandonas el modelo de programación simple o desperdicias los hilos.

El modelo F # ofrece lo mejor de ambos mundos; no bloquea hilos, pero mantiene el modelo de programación directo. Construcciones como let! te permite ''roscar-saltar'' en el medio de un bloque asíncrono, por lo que en código como

Blah1() let! x = AsyncOp() Blah2()

Blah1 puede ejecutarse, por ejemplo, en el subproceso ThreadPool # 13, pero luego AsyncOp lo liberará nuevamente al ThreadPool. Más tarde, cuando se complete el AsyncOp, el resto del código comenzará una copia de seguridad en un hilo disponible (tal vez, por ejemplo, subproceso ThreadPool # 20) que vincula x al resultado y luego ejecuta Blah2 . En aplicaciones de cliente triviales, esto rara vez importa (excepto cuando se asegura que no bloquea el hilo de UI), sino en aplicaciones de servidor que hacen E / S (donde los hilos son a menudo un recurso precioso; los hilos son costosos y no se pueden desperdiciar por bloqueo) la E / S sin bloqueo a menudo es la única forma de crear una escala de aplicación. F # le permite escribir E / S sin bloqueo sin que el programa se degrade en una gran cantidad de devoluciones de códigos de espagueti.

Ver también

Mejores prácticas para paralelizar el uso del flujo de trabajo asincrónico

¿Cómo hacer devoluciones de llamada encadenadas en F #?

http://cs.hubfs.net/forums/thread/8262.aspx


No se trata de "ganar tiempo". La programación asincrónica no hará que los datos lleguen más rápido. Más bien, se trata de simplificar el modelo mental para la concurrencia.

En C #, por ejemplo, si desea realizar una operación asíncrona, debe comenzar a rebuscar con devoluciones de llamadas y pasar el estado local a esas devoluciones de llamada, y así sucesivamente. Para una operación simple como la de Expert F # con dos operaciones asincrónicas, estás viendo tres métodos aparentemente independientes (el iniciador y dos devoluciones de llamada). Esto disfraza la naturaleza secuencial y conceptualmente lineal del flujo de trabajo: hacer solicitud, leer flujo, imprimir resultados.

Por el contrario, el código de flujo de trabajo asíncrono F # hace que la secuencia del programa sea muy clara. Puede ver exactamente qué está sucediendo en qué orden con solo mirar el bloque de código. No es necesario perseguir las devoluciones de llamada.

Dicho esto, F # tiene mecanismos que pueden ayudar a ahorrar tiempo si hay varias operaciones asincrónicas independientes en progreso. Por ejemplo, puede iniciar múltiples flujos de trabajo asíncronos al mismo tiempo, y se ejecutarán en paralelo. Pero dentro de una sola instancia de flujo de trabajo asíncrono, se trata principalmente de simplicidad, seguridad y comprensibilidad: acerca de permitir razonar acerca de secuencias asincrónicas de enunciados tan fácilmente como usted razona acerca de las secuencias sincrónicas de sentencias de estilo C #.