Compartilhar 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 outro trabalho. Por exemplo, cálculos assíncronos podem ser usados para gravar aplicativos com UIs que permanecem responsivos aos usuários à medida que o aplicativo executa outro trabalho. O modelo de programação do Workflow Assíncrono F# permite escrever programas funcionais enquanto oculta os detalhes da transição de threads dentro de uma biblioteca.

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

Sintaxe

async { expression }

Observações

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. 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>, em que 'T é o tipo retornado pela expressão quando a palavra-chave return é usada.

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

Associação Assíncrona usando LET!

Em uma expressão assíncrona, algumas expressões e operações são síncronas e outras são assíncronas. Quando você chama um método de forma assíncrona, em vez de uma associação de let comum, você usa let!. O efeito de let! é habilitar a execução para continuar em outras computações ou threads, à medida que a computação está sendo executada. Depois que o lado direito da associação let! é retornado, o restante da expressão assíncrona retoma a 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 a computação 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 para aguardar computações assíncronas do F# Async<T> diretamente. Você pode aguardar outros tipos de operações assíncronas indiretamente:

  • Tarefas do .NET, Task<TResult> e o Task não genérico, combinando com Async.AwaitTask
  • Tarefas de valor do .NET, ValueTask<TResult> e o ValueTask não genérico, combinando com .AsTask() e Async.AwaitTask
  • Qualquer objeto que segue o padrão "GetAwaiter" especificado no F# RFC FS-1097, quando combinado com task { return! expr } |> Async.AwaitTask.

Fluxo de controle

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

As expressões assíncronas do F# não dão suporte a try .. finally .. assíncrono. Você pode usar uma expressão de tarefa para esse caso.

Associações use e use!

Dentro de expressões assíncronas, as associações de use podem ser associadas a valores do tipo IDisposable. Nesse último caso, a operação de limpeza de 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. Para use!, o objeto é descartado 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 o 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!. Diversos primitivos assíncronos são definidos na biblioteca central de 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 para uma página da Web.

Vários primitivos para operações assíncronas de E/S são incluídos 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 seus próprios primitivos assíncronos definindo uma função ou método cujo corpo é uma expressão assíncrona.

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

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

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 associação é feita ao resultado de um primitivo assíncrono, nesse caso AsyncDownloadString, let! é usado em vez de let.

Você usa a função Async.RunSynchronously para executar uma operação assíncrona e aguardar o resultado. Por exemplo, você pode executar várias operações assíncronas em paralelo usando a função Async.Parallel junto 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 a 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 elas 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()

Consulte também