Partilhar via


Expressões assíncronas

Este artigo descreve o suporte em F# para expressões assíncronas. As expressões assíncronas fornecem uma maneira de executar cálculos de forma assíncrona, ou seja, sem bloquear a execução de outros trabalhos. Por exemplo, cálculos assíncronos podem ser usados para escrever aplicativos que tenham interfaces do usuário que permaneçam responsivas aos usuários à medida que o aplicativo executa outro trabalho. O modelo de programação F# Asynchronous Workflows permite escrever programas funcionais enquanto oculta os detalhes da transição de threads em uma biblioteca.

O código assíncrono também pode ser criado usando expressões de tarefa, que criam tarefas .NET diretamente. O uso de expressões de tarefa é preferível ao interoperar extensivamente com bibliotecas .NET que criam ou consomem tarefas .NET. Ao escrever a maioria dos códigos assíncronos em F#, as expressões assíncronas F# são preferidas porque são mais sucintas, mais composicionais e evitam certas ressalvas associadas a tarefas .NET.

Sintaxe

async { expression }

Comentários

Na sintaxe anterior, a computação representada por expression é configurada para ser executada de forma assíncrona, ou seja, sem bloquear o thread de computação atual quando operações de suspensão assíncronas, E/S e outras operações assíncronas são executadas. Os cálculos assíncronos geralmente são iniciados em um thread em segundo plano enquanto a execução continua no thread atual. O tipo da expressão é Async<'T>, onde 'T é o tipo retornado pela expressão quando a palavra-chave return é usada.

A classe Async fornece métodos que oferecem suporte a vários cenários. A abordagem geral é criar Async objetos que representem a computação ou cálculos que você deseja executar de forma assíncrona e, em seguida, iniciar esses cálculos usando uma das funções de acionamento. O acionamento que você usa depende se você deseja usar o thread atual, um thread em segundo plano ou um objeto de tarefa .NET. Por exemplo, para iniciar um cálculo assíncrono no thread atual, você pode usar Async.StartImmediate. Ao iniciar uma computação assíncrona a partir do thread da interface do usuário, você não bloqueia o loop de evento principal que processa ações do usuário, como pressionamentos de teclas e atividade do mouse, para que seu aplicativo permaneça responsivo.

Vinculação assíncrona usando let!

Em uma expressão assíncrona, algumas expressões e operações são síncronas e outras assíncronas. Quando você chama um método de forma assíncrona, em vez de uma associação let comum, você usa let!. O efeito do let! é permitir que a execução continue em outras computações ou threads à medida que a computação está a ser realizada. Após o lado direito da vinculação let! retornar, a restante expressão assíncrona retoma a sua execução.

O código a seguir mostra a diferença entre let e let!. A linha de código que usa let apenas cria uma computação assíncrona como um objeto que você pode executar posteriormente usando, por exemplo, Async.StartImmediate ou Async.RunSynchronously. A linha de código que usa let! inicia o cálculo e executa uma espera assíncrona: o thread é suspenso até que o resultado esteja disponível, momento em que a execução continua.

// 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! só pode ser usado diretamente para aguardar cálculos assíncronos de F# Async<T>. Você pode aguardar outros tipos de operações assíncronas indiretamente:

  • Tarefas .NET, Task<TResult> e Tasknão genérico, em conjunto com Async.AwaitTask
  • Tarefas de valor do .NET, combinando o não genérico ValueTaskcom ValueTask<TResult>, .AsTask() e Async.AwaitTask
  • Qualquer objeto seguindo o padrão "GetAwaiter" especificado em F# RFC FS-1097, em combinação com task { return! expr } |> Async.AwaitTask.

Fluxo de controle

As expressões assíncronas podem incluir construções de fluxo de controle, como for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. elsee if .. then ... Estes, por sua vez, podem incluir outras construções assíncronas, com exceção dos manipuladores with e finally, que são executados de forma síncrona.

As expressões assíncronas F# não suportam try .. finally ..assíncronas. Você pode usar uma expressão de tarefa para este caso.

use e use! vinculações

Dentro de expressões assíncronas, use ligações podem vincular-se a valores do tipo IDisposable. Para o último caso, a operação de limpeza do descarte é executada de forma assíncrona.

Além de let!, você pode usar use! para executar associações assíncronas. A diferença entre let! e use! é a mesma que a diferença entre let e use. Por use!, o objeto é eliminado no fechamento do escopo atual. Observe que na versão atual do F#, use! não permite que um valor seja inicializado como nulo, mesmo que use faça.

Primitivos assíncronos

Um método que executa uma única tarefa assíncrona e retorna o resultado é chamado de primitivo assíncrono , e eles são projetados especificamente para uso com let!. Várias primitivas assíncronas são definidas na biblioteca principal do F#. Dois desses métodos para aplicativos Web são definidos no módulo FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse e HttpClient.GetStringAsync (encapsulados com Async.AwaitTask para compatibilidade com o modelo assíncrono do F#). Ambos os primitivos baixam dados de uma página da Web, dada uma URL. AsyncGetResponse produz um objeto System.Net.WebResponse e GetStringAsync produz uma cadeia de caracteres que representa o HTML de uma página da Web.

Várias primitivas para operações de E/S assíncronas estão incluídas no módulo FSharp.Control.CommonExtensions. Esses métodos de extensão da classe System.IO.Stream são Stream.AsyncRead e Stream.AsyncWrite.

Você também pode escrever suas próprias primitivas assíncronas definindo uma função ou método cujo corpo é uma expressão assíncrona.

Para usar métodos assíncronos no .NET Framework que são projetados para outros modelos assíncronos com o modelo de programação assíncrona F#, crie uma função que retorna um objeto F# Async. A biblioteca F# tem funções que facilitam isso.

Um exemplo de uso de expressões assíncronas está incluído aqui; há muitos outros na documentação para os métodos do classe Async.

Este exemplo mostra como usar expressões assíncronas para executar código em paralelo.

No exemplo de código a seguir, uma função fetchAsync obtém o texto HTML retornado de uma solicitação da Web. A função fetchAsync contém um bloco assíncrono de código. Quando uma ligação é feita para o resultado de uma primitiva assíncrona, neste caso AsyncDownloadString, let! é usado em vez de let.

Use a função Async.RunSynchronously para executar uma operação assíncrona e aguardar seu resultado. Como exemplo, você pode executar várias operações assíncronas em paralelo usando a função Async.Parallel juntamente com a função Async.RunSynchronously. A função Async.Parallel usa uma lista dos objetos Async, configura o código para cada objeto de tarefa Async para ser executado em paralelo e retorna um objeto Async que representa a computação paralela. Assim como para uma única operação, você chama Async.RunSynchronously para iniciar a execução.

A função runAll inicia três expressões assíncronas em paralelo e aguarda até que todas sejam concluídas.

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()

Ver também