Programación asincrónica en F#Async Programming in F#

Nota

Se han descubierto algunas imprecisiones en este artículo.Some inaccuracies have been discovered in this article. Se está sobrescribiendo.It is being rewritten. Consulte problema #666 para obtener información acerca de los cambios.See Issue #666 to learn about the changes.

Programación de Async F# puede realizarse a través de un modelo de programación de nivel de lenguaje diseñado para ser fácil de usar y el lenguaje natural.Async programming in F# can be accomplished through a language-level programming model designed to be easy to use and natural to the language.

El núcleo de la programación de async F# es Async<'T>, una representación de trabajo que puede activarse para ejecutar en segundo plano, donde 'T se devuelve el tipo a través de especial return palabra clave o unit si el flujo de trabajo asincrónico no tiene ningún resultado para devolver.The core of async programming in F# is Async<'T>, a representation of work that can be triggered to run in the background, where 'T is either the type returned via the special return keyword or unit if the async workflow has no result to return.

El concepto clave para comprender es que el tipo de una expresión de async es Async<'T>, que es simplemente un especificación de trabajo se realice en un contexto asincrónico.The key concept to understand is that an async expression’s type is Async<'T>, which is merely a specification of work to be done in an asynchronous context. No se ejecuta hasta que lo inicie explícitamente con una de las funciones de iniciales (como Async.RunSynchronously).It is not executed until you explicitly start it with one of the starting functions (such as Async.RunSynchronously). Aunque se trata de una forma diferente de reflexionar sobre cómo realizar el trabajo termina siendo bastante sencillo en la práctica.Although this is a different way of thinking about doing work, it ends up being quite simple in practice.

Por ejemplo, supongamos que desea descargar el código HTML de dotnetfoundation.org sin bloquear el subproceso principal.For example, say you wanted to download the HTML from dotnetfoundation.org without blocking the main thread. Puede realizarlo siguiente:You can accomplish it like this:

open System
open System.Net

let fetchHtmlAsync url =
    async {
        let uri = Uri(url)
        use webClient = new WebClient()

        // Execution of fetchHtmlAsync won't continue until the result
        // of AsyncDownloadString is bound.
        let! html = webClient.AsyncDownloadString(uri)
        return html
    }

let html = "https://dotnetfoundation.org" |> fetchHtmlAsync |> Async.RunSynchronously
printfn "%s" html

Y listo.And that’s it! Aparte del uso de async, let!, y return, esto es normal F# código.Aside from the use of async, let!, and return, this is just normal F# code.

Hay algunas construcciones sintácticas que merece la pena tener en cuenta:There are a few syntactical constructs which are worth noting:

  • let! enlaza el resultado de una expresión de async (que se ejecuta en otro contexto).let! binds the result of an async expression (which runs on another context).
  • use! funciona igual que let!, pero elimina sus recursos enlazados cuando sale del ámbito.use! works just like let!, but disposes its bound resources when it goes out of scope.
  • do! se espera un flujo de trabajo asincrónico que no devuelve nada.do! will await an async workflow which doesn’t return anything.
  • return simplemente devuelve un resultado de una expresión de async.return simply returns a result from an async expression.
  • return! ejecuta otro flujo de trabajo asincrónico y devuelve su valor devuelto como resultado.return! executes another async workflow and returns its return value as a result.

Además, normal let, use, y do palabras clave pueden utilizarse junto con las versiones asincrónicas tal como haría en una función normal.Additionally, normal let, use, and do keywords can be used alongside the async versions just as they would in a normal function.

Cómo iniciar el código asincrónico en F#How to start Async Code in F#

Como se mencionó anteriormente, el código asincrónico es una especificación de trabajo se realice en otro contexto que se debe iniciar explícitamente.As mentioned earlier, async code is a specification of work to be done in another context which needs to be explicitly started. Existen dos formas principales para lograr esto:Here are two primary ways to accomplish this:

  1. Async.RunSynchronously iniciará un flujo de trabajo asincrónico en otro subproceso y espera su resultado.Async.RunSynchronously will start an async workflow on another thread and await its result.

    open System
    open System.Net
    
    let fetchHtmlAsync url =
        async {
            let uri = Uri(url)
            use webClient = new WebClient()
            let! html = webClient.AsyncDownloadString(uri)
            return html
        }
    
    // Execution will pause until fetchHtmlAsync finishes
    let html = "https://dotnetfoundation.org" |> fetchHtmlAsync |> Async.RunSynchronously
    
    // you actually have the result from fetchHtmlAsync now!
    printfn "%s" html
    
  2. Async.Start iniciar un flujo de trabajo de async en otro subproceso y, le no espera su resultado.Async.Start will start an async workflow on another thread, and will not await its result.

    open System
    open System.Net
    
    let uploadDataAsync url data =
        async {
            let uri = Uri(url)
            use webClient = new WebClient()
            webClient.UploadStringAsync(uri, data)
        }
    
    let workflow = uploadDataAsync "https://url-to-upload-to.com" "hello, world!"
    
    // Execution will continue after calling this!
    Async.Start(workflow)
    
    printfn "%s" "uploadDataAsync is running in the background..."
    

Hay otras maneras de iniciar un flujo de trabajo asincrónicos disponible para escenarios más específicos.There are other ways to start an async workflow available for more specific scenarios. Se detallan en la referencia de Async.They are detailed in the Async reference.

Una nota en subprocesosA Note on Threads

La frase "en otro subproceso" esté mencionada anteriormente, pero es importante saber que esto no significa que los flujos de trabajo asincrónicos son una fachada para multithreading.The phrase "on another thread" is mentioned above, but it is important to know that this does not mean that async workflows are a facade for multithreading. El flujo de trabajo realmente "salta" entre subprocesos, de préstamo de una pequeña cantidad de tiempo para realizar trabajo útil.The workflow actually "jumps" between threads, borrowing them for a small amount of time to do useful work. Cuando un flujo de trabajo asincrónico está eficazmente "esperando" (por ejemplo, esperando una llamada de red devolver algo), se libera cualquier subproceso que se toma prestado en el momento hasta vaya realice trabajo útil en alguna otra cosa.When an async workflow is effectively "waiting" (for example, waiting for a network call to return something), any thread it was borrowing at the time is freed up to go do useful work on something else. Esto permite flujos de trabajo asincrónicos para que usen el sistema que se ejecutan en forma más eficaz posible y hace que sea especialmente seguros para escenarios de E/S de gran volumen.This allows async workflows to utilize the system they run on as effectively as possible, and makes them especially strong for high-volume I/O scenarios.

Cómo agregar paralelismo a código asincrónicoHow to Add Parallelism to Async Code

En ocasiones, es posible que necesita para realizar varias tareas asincrónicas en paralelo, recopilar sus resultados e interpretarlos de alguna manera.Sometimes you may need to perform multiple asynchronous jobs in parallel, collect their results, and interpret them in some way. Async.Parallel permite realizar esto sin necesidad de usar la biblioteca TPL, lo que implicaría la necesidad de convertir Task<'T> y Async<'T> tipos.Async.Parallel allows you to do this without needing to use the Task Parallel Library, which would involve needing to coerce Task<'T> and Async<'T> types.

En el ejemplo siguiente, se usará Async.Parallel para descargar el código HTML de cuatro sitios más populares en paralelo, espere a que esas tareas se completen y, a continuación, imprima el código HTML que se descargó.The following example will use Async.Parallel to download the HTML from four popular sites in parallel, wait for those tasks to complete, and then print the HTML which was downloaded.

open System
open System.Net

let urlList =
    [ "https://www.microsoft.com"
      "https://www.google.com"
      "https://www.amazon.com"
      "https://www.facebook.com" ]

let fetchHtmlAsync url =
    async {
        let uri = Uri(url)
        use webClient = new WebClient()
        let! html = webClient.AsyncDownloadString(uri)
        return html
    }

let getHtmlList urls =
    urls
    |> Seq.map fetchHtmlAsync   // Build an Async<'T> for each site
    |> Async.Parallel           // Returns an Async<'T []>
    |> Async.RunSynchronously   // Wait for the result of the parallel work

let htmlList = getHtmlList urlList

// We now have the downloaded HTML for each site!
for html in htmlList do
    printfn "%s" html

Consejos e información importanteImportant Info and Advice

  • Anexa "Async" al final de cualquier función que se va a consumirAppend "Async" to the end of any functions you’ll consume

Aunque se trata de una convención de nomenclatura, lo facilitar las cosas como la detectabilidad de las API.Although this is just a naming convention, it does make things like API discoverability easier. Especialmente si hay versiones sincrónicas y asincrónicas de la misma rutina, es una buena idea para indicar explícitamente que es asincrónico por medio del nombre.Particularly if there are synchronous and asynchronous versions of the same routine, it’s a good idea to explicitly state which is asynchronous via the name.

  • Escuchar al compilador.Listen to the compiler!

F#del compilador es muy estricto, lo que casi imposible hacer algo problemáticos, como ejecutar "async" código de forma sincrónica.F#’s compiler is very strict, making it nearly impossible to do something troubling like run "async" code synchronously. Si llega a través de una advertencia, que es un inicio de sesión que el código no ejecutará cómo cree que lo hará.If you come across a warning, that’s a sign that the code won’t execute how you think it will. Si puede hacer que el compilador feliz, más probable es que se ejecutará el código según lo previsto.If you can make the compiler happy, your code will most likely execute as expected.

Para el C#programador/VB estudiando F#For the C#/VB Programmer Looking Into F#

En esta sección se da por supuesto que está familiarizado con el modelo asincrónico en C#/VB.This section assumes you’re familiar with the async model in C#/VB. Si no, de programación asincrónica en C# es un punto de partida.If you are not, Async Programming in C# is a starting point.

Hay una diferencia fundamental entre el C#/VB async modelo y el F# modelo asincrónico.There is a fundamental difference between the C#/VB async model and the F# async model.

Cuando se llama a una función que devuelve un Task o Task<'T>, ese trabajo ya ha comenzado la ejecución.When you call a function which returns a Task or Task<'T>, that job has already begun execution. El identificador devuelto representa un trabajo asincrónico está ya en ejecución.The handle returned represents an already-running asynchronous job. En cambio, cuando se llama a una función asincrónica F#, Async<'a> devuelto representa un trabajo que será genera en algún momento.In contrast, when you call an async function in F#, the Async<'a> returned represents a job which will be generated at some point. Descripción de este modelo es eficaz, porque permite trabajos asincrónicos en F# se pueden encadenar, realiza de forma condicional y se puede iniciar con un nivel de detalle más preciso de control.Understanding this model is powerful, because it allows for asynchronous jobs in F# to be chained together easier, performed conditionally, and be started with a finer grain of control.

Hay algunos otros las similitudes y diferencias cabe destacar.There are a few other similarities and differences worth noting.

SimilitudesSimilarities

  • let!, use!, y do! son análogos a await al llamar a un trabajo asincrónico desde una async{ } bloque.let!, use!, and do! are analogous to await when calling an async job from within an async{ } block.

    Las tres palabras clave solo se pueden usar dentro de un async { } bloque, similar a cómo await solo puede invocarse dentro de un async método.The three keywords can only be used within an async { } block, similar to how await can only be invoked inside an async method. En resumen, let! es para cuando desee capturar y utilizar un resultado, use! es el mismo pero algo cuyos recursos deben obtener limpiados tras su uso, y do! es para cuando desee esperar un flujo de trabajo asincrónico sin ningún valor devuelto al finalizar antes de continuar.In short, let! is for when you want to capture and use a result, use! is the same but for something whose resources should get cleaned after it’s used, and do! is for when you want to wait for an async workflow with no return value to finish before moving on.

  • F#admite el paralelismo de datos de forma similar.F# supports data-parallelism in a similar way.

    Aunque funciona de forma muy distinta, Async.Parallel corresponde a Task.WhenAll en el caso de que los resultados de un conjunto de trabajos asincrónicos cuando se completan.Although it operates very differently, Async.Parallel corresponds to Task.WhenAll for the scenario of wanting the results of a set of async jobs when they all complete.

DiferenciasDifferences

  • Anidar let! no se permite, a diferencia de anidados awaitNested let! is not allowed, unlike nested await

    A diferencia de await, que se pueden anidar indefinidamente, let! no se puede y debe tener su resultado enlazados antes de usarlo dentro de otra let!, do!, o use!.Unlike await, which can be nested indefinitely, let! cannot and must have its result bound before using it inside of another let!, do!, or use!.

  • Compatibilidad con la cancelación es más sencillo en F# que en C#/VB.Cancellation support is simpler in F# than in C#/VB.

    Admitir la cancelación de una tarea a mitad de camino su ejecución en C#/VB requiere comprobación el IsCancellationRequested propiedad o llamar a ThrowIfCancellationRequested() en un CancellationToken objeto que se pasa al método asincrónico.Supporting cancellation of a task midway through its execution in C#/VB requires checking the IsCancellationRequested property or calling ThrowIfCancellationRequested() on a CancellationToken object that’s passed into the async method.

En cambio, F# flujos de trabajo asincrónicos son cancelables de manera más natural.In contrast, F# async workflows are more naturally cancellable. La cancelación es un proceso sencillo de tres pasos.Cancellation is a simple three-step process.

  1. Cree un nuevo objeto CancellationTokenSource.Create a new CancellationTokenSource.
  2. Pasarlo a una función inicial.Pass it into a starting function.
  3. Llamar a Cancel en el token.Call Cancel on the token.

Ejemplo:Example:

open System.Threading

// Create a workflow which will loop forever.
let workflow =
    async {
        while true do
            printfn "Working..."
            do! Async.Sleep 1000
    }

let tokenSource = new CancellationTokenSource()

// Start the workflow in the background
Async.Start (workflow, tokenSource.Token)

// Executing the next line will stop the workflow
tokenSource.Cancel()

Y listo.And that’s it!

Recursos adicionales:Further resources: