Flujos de trabajo asincrónicos (F#)
En este tema, se describe la compatibilidad de F# con la ejecución asincrónica de los cálculos, es decir, sin que se bloquee la ejecución de otros trabajos. Por ejemplo, los cálculos asincrónicos se pueden usar para crear aplicaciones con interfaces de usuario que sigan respondiendo a los usuarios mientras la aplicación realiza otras tareas.
async { expression }
Comentarios
En la sintaxis anterior, el cálculo representado por expression está configurado para ejecutarse de forma asincrónica, es decir, sin bloquear el subproceso de cálculo actual cuando se realizan operaciones en suspensión asincrónicas, E/S y otras operaciones asincrónicas. Los cálculos asincrónicos se inician a menudo en un subproceso de fondo mientras la ejecución continúa en el subproceso actual. El tipo de la expresión es Async<'a>, donde 'a es el tipo devuelto por la expresión cuando se utiliza la palabra clave return. El código en este tipo de expresión se denomina bloque asincrónico.
Existen diversas formas de programación asincrónica y la clase Async proporciona métodos que se pueden usar en varios escenarios. Generalmente, se crean objetos Async que representan los cálculos que se desean ejecutar de forma asincrónica y, a continuación, se inician estos cálculos mediante una de las funciones desencadenadoras. Las diversas funciones desencadenadoras proporcionan diferentes maneras de ejecutar cálculos asincrónicos. La elección depende de si se desea usar el subproceso actual, un subproceso en segundo plano o un objeto de tarea de .NET Framework, y también depende de si hay funciones de continuación que deben ejecutarse al término del cálculo. Por ejemplo, para iniciar un cálculo asincrónico en el subproceso actual, puede utilizar Async.StartImmediate. Cuando se inicia un cálculo asincrónico desde el subproceso de la interfaz de usuario, no se bloquea el bucle de eventos principal que procesa las acciones del usuario, como las pulsaciones de teclas y la actividad del mouse, por lo que la aplicación sigue respondiendo.
Enlace asincrónico mediante let!
En un flujo de trabajo asincrónico, algunas expresiones y operaciones son sincrónicas, y otras son más largas y están diseñadas para devolver el resultado de forma asincrónica. Cuando se llama de forma asincrónica a un método, se utiliza un enlace let! en lugar de un enlace let normal. El enlace let! permite que continúe la ejecución de otros cálculos o subprocesos mientras se realiza el cálculo. Una vez devuelto el resultado de evaluar el lado derecho del enlace let!, el resto del flujo de trabajo asincrónico reanuda la ejecución.
En el siguiente código, se muestra la diferencia entre let y let!. La línea de código en la que se utiliza let crea simplemente un cálculo asincrónico como un objeto que se puede ejecutar más adelante usando, por ejemplo, Async.StartImmediate o Async.RunSynchronously. La línea de código en la que se utiliza let! inicia el cálculo y, a continuación, se suspende el subproceso hasta que el resultado esté disponible. En ese preciso momento, continuará la ejecución.
// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[]) = stream.AsyncRead(bufferSize)
Además de let!, puede usar use! para realizar enlaces asincrónicos. La diferencia entre let! y use! es igual que la diferencia entre let y use. Para use!, el objeto se elimina en el cierre del ámbito actual. Tenga en cuenta que, en la versión actual del lenguaje F#, use! no permite que un valor se inicialice como null, aunque use lo permite.
Primitivos asincrónicos
Un método que realiza una sola tarea asincrónica y devuelve el resultado se denomina primitivo asincrónico. Estos métodos están especialmente diseñados para usarlos con let!. En la biblioteca básica de F#, se definen varios métodos primitivos asincrónicos. En el módulo Microsoft.FSharp.Control.WebExtensions, se definen dos de estos métodos para aplicaciones web: WebRequest.AsyncGetResponse y WebClient.AsyncDownloadString. Ambos métodos primitivos descargan datos de la página web con la dirección URL especificada. AsyncGetResponse genera un objeto WebResponse, y AsyncDownloadString genera una cadena que representa el texto HTML de una página web.
El módulo Microsoft.FSharp.Control.CommonExtensions incluye varios métodos primitivos para las operaciones de E/S asincrónicas. Los métodos de extensión de la clase Stream son Stream.AsyncRead y Stream.AsyncWrite.
F# PowerPack incluye más métodos primitivos asincrónicos. Además, puede escribir sus propios métodos primitivos asincrónicos definiendo una función cuyo cuerpo se encuentre totalmente en un bloque asincrónico.
Para utilizar con el modelo de programación asincrónica de F# los métodos asincrónicos de .NET Framework que se han diseñado para otros modelos asincrónicos, debe crear una función que devuelva un objeto Async de F#. La biblioteca de F# incluye funciones que facilitan esta tarea.
Aquí se incluye un ejemplo de uso de los flujos de trabajo asincrónicos; en la documentación sobre los métodos de la clase Async puede encontrar muchos más.
Ejemplo
En este ejemplo, se muestra cómo utilizar los flujos de trabajo asincrónicos para realizar cálculos en paralelo.
En el siguiente ejemplo de código, la función fetchAsync obtiene el texto HTLM devuelto por una solicitud web. La función fetchAsync contiene un bloque asincrónico de código. Cuando se realiza un enlace al resultado de un método primitivo asincrónico (en este caso, AsyncDownloadString), se utiliza let! en lugar de let.
Se usa la función Async.RunSynchronously para ejecutar una operación asincrónica y aguardar su resultado. Como ejemplo, puede ejecutar en paralelo varias operaciones asincrónicas usando la función Async.Parallel junto con la función Async.RunSynchronously. La función Async.Parallel toma una lista de objetos Async, configura el código para cada objeto de tarea Async y devuelve un objeto Async que representa el cálculo que se ejecuta en paralelo. Al igual que en el caso de una sola operación, se llama a Async.RunSynchronously para iniciar la ejecución.
La función runAll inicia tres flujos de trabajo asincrónicos en paralelo y espera hasta que se hayan completado todos.
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let urlList = [ "Microsoft.com", "https://www.microsoft.com/"
"MSDN", "https://msdn.microsoft.com/"
"Bing", "https://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
printfn "Read %d characters for %s" html.Length name
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
runAll()