Compartir a través de


Expresiones asincrónicas

En este artículo se describe la compatibilidad de F# con las expresiones asincrónicas. Las expresiones asincrónicas proporcionan una manera de realizar cálculos de forma asincrónica, es decir, sin bloquear la ejecución de otro trabajo. Por ejemplo, los cálculos asincrónicos se pueden usar para escribir aplicaciones con interfaces de usuario que siguen respondiendo a los usuarios mientras la aplicación realiza otro trabajo. El modelo de programación de flujos de trabajo asincrónicos de F# le permite escribir programas funcionales al ocultar los detalles de la transición de subprocesos dentro de una biblioteca.

El código asincrónico también se puede crear mediante Expresiones de tareas, que crean tareas de .NET directamente. Se prefiere usar expresiones de tarea cuando se interopera mucho con bibliotecas de .NET que crean o consumen tareas de .NET. Cuando la mayoría del código asincrónico se escribe en F#, se prefieren expresiones asincrónicas de F# porque son más concisas, más composicionales y evitan ciertas advertencias asociadas a las tareas de .NET.

Sintaxis

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 asincrónicas de suspensión, E/S y otras. Los cálculos asincrónicos se suelen iniciar en un subproceso en segundo plano mientras la ejecución continúa en el subproceso actual. El tipo de la expresión es Async<'T>, donde 'T es el tipo devuelto por la expresión cuando se usa la palabra clave return.

La clase Async proporciona métodos que admiten varios escenarios. El enfoque general consiste en crear objetos Async que representan el o los cálculos que se quieren ejecutar de forma asincrónica y, luego, en iniciar estos cálculos mediante una de las funciones desencadenadoras. El desencadenador que se use depende de si se quiere usar el subproceso actual, un subproceso en segundo plano o un objeto de tarea de .NET. Por ejemplo, para iniciar un cálculo asincrónico en el subproceso actual, puede usar Async.StartImmediate. Cuando se inicia un cálculo asincrónico desde el subproceso de interfaz de usuario no se bloquea el bucle de eventos principal que procesa acciones de usuario como pulsaciones de tecla y actividad del mouse, por lo que la aplicación sigue respondiendo.

Enlace asincrónico mediante let!

En una expresión asincrónica, algunas expresiones y operaciones son sincrónicas y otras asincrónicas. Cuando se llama a un método de forma asincrónica, en lugar de un enlace normal let, se usa let!. El efecto de let! es permitir que la ejecución continúe en otros cálculos o subprocesos mientras se realiza el cálculo. Después de que se devuelva el lado derecho del enlace let!, el resto de la expresión asincrónica reanuda la ejecución.

En el código siguiente se muestra la diferencia entre let y let!. La línea de código que usa let simplemente crea un cálculo asincrónico como un objeto que se puede ejecutar más adelante mediante, por ejemplo, Async.StartImmediate o Async.RunSynchronously. La línea de código que usa let! inicia el cálculo y realiza una espera asincrónica: el subproceso se suspende hasta que el resultado está disponible, momento en que continúa 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)

let! solo se puede usar para esperar cálculos asincrónicos de F# Async<T> directamente. Puede esperar otros tipos de operaciones asincrónicas indirectamente:

  • Tareas de .NET, Task<TResult> y Task no genérico, mediante la combinación con Async.AwaitTask
  • Tareas de valor de .NET, ValueTask<TResult> y ValueTask no genérico, mediante la combinación con .AsTask() y Async.AwaitTask
  • Cualquier objeto que siga el patrón "GetAwaiter" especificado en F# RFC FS-1097, mediante la combinación con task { return! expr } |> Async.AwaitTask.

Flujo de control

Las expresiones asincrónicas pueden incluir construcciones de flujo de control, como for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. else e if .. then ... Estas pueden, a su vez, incluir construcciones asincrónicas adicionales, con la excepción de los controladores with y finally, que se ejecutan de forma sincrónica.

Las expresiones asincrónicas de F# no admiten try .. finally .. asincrónico. En este caso puede usar una expresión de tarea.

Enlaces use y use!

Dentro de las expresiones asincrónicas, los enlaces use pueden enlazarse a valores de tipo IDisposable. En este último, la operación de limpieza de eliminación se ejecuta de forma asincrónica.

Además de let!, puede usar use! para realizar enlaces asincrónicos. La diferencia entre let! y use! es la misma que la diferencia entre let y use. En use!, el objeto se elimina al cerrar el ámbito actual. Tenga en cuenta que, en la versión actual de F#, use! no permite inicializar un valor en null, aunque use sí.

Primitivos asincrónicos

Un método que realiza una sola tarea asincrónica y devuelve el resultado se conoce como primitivo asincrónico, y está diseñado específicamente para su uso con let!. En la biblioteca principal de F# se definen varios primitivos asincrónicos. En el módulo FSharp.Control.WebExtensions se definen dos métodos de este tipo para aplicaciones web: WebRequest.AsyncGetResponse y WebClient.AsyncDownloadString. Ambos primitivos descargan datos de una página web, siempre que tengan una dirección URL. AsyncGetResponse genera un objeto System.Net.WebResponse y AsyncDownloadString genera una cadena que representa el HTML de una página web.

En el módulo FSharp.Control.CommonExtensions se incluyen varios primitivos para operaciones de E/S asincrónicas. Estos métodos de extensión de la clase System.IO.Stream son Stream.AsyncRead y Stream.AsyncWrite.

También puede escribir sus propios primitivos asincrónicos si define una función o un método cuyo cuerpo sea una expresión asincrónica.

Para usar métodos asincrónicos en .NET Framework diseñados para otros modelos asincrónicos con el modelo de programación asincrónica de F#, cree una función que devuelva un objeto Async de F#. La biblioteca de F# tiene funciones que facilitan esta tarea.

Aquí se incluye un ejemplo de uso de expresiones asincrónicas; hay muchos otros en la documentación de los métodos de la clase Async.

En este ejemplo se muestra cómo usar expresiones asincrónicas para ejecutar código en paralelo.

En el ejemplo de código siguiente, una función fetchAsync obtiene el texto HTML devuelto desde una solicitud web. La función fetchAsync contiene un bloque asincrónico de código. Cuando se realiza un enlace al resultado de un primitivo asincrónico, en este caso AsyncDownloadString, se usa let! en lugar de let.

La función Async.RunSynchronously se usa para ejecutar una operación asincrónica y esperar su resultado. Como ejemplo, puede ejecutar varias operaciones asincrónicas en paralelo mediante la función Async.Parallel junto con la función Async.RunSynchronously. La función Async.Parallel toma una lista de los objetos Async, configura el código para que cada objeto de tarea Async se ejecute en paralelo y devuelve un objeto Async que representa el cálculo paralelo. Al igual que en una sola operación, se llama a Async.RunSynchronously para iniciar la ejecución.

La función runAll inicia tres expresiones asincrónicas en paralelo y espera hasta que todas se completan.

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                "MSDN", "http://msdn.microsoft.com/"
                "Bing", "http://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()

Consulte también