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()
yAsync.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
open System.Net.Http
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 httpClient = new HttpClient()
let! html = httpClient.GetStringAsync(uri) |> Async.AwaitTask
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()