Expressões assíncronas

Este artigo descreve o suporte do F# para expressões assíncronas. As expressões assíncronas fornecem uma maneira de realizar computações de forma assíncrona, ou seja, sem bloquear a execução de outro trabalho. Por exemplo, as computações assíncronas podem ser usadas para gravas aplicativos que têm IUs que continuam respondendo aos usuários enquanto 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 as tarefas do .NET diretamente. O uso de expressões de tarefa é preferencial ao interoperar extensivamente com as bibliotecas do .NET que criam ou consomem as tarefas do .NET. Ao gravar o código mais assíncrono no F#, as expressões assíncronas do F# são preferenciais porque são mais concisas, mais composicionais e evitam determinadas advertências associadas às tarefas do .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 durante a realização das operações assíncronas de suspensão, E/S e outras operações assíncronas. Geralmente, as computações assíncronas são iniciadas 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 dão suporte a vários cenários. A abordagem geral é criar objetos Async que representem a computação ou as computações que você deseja executar de forma assíncrona e, em seguida, iniciar essas computações usando uma das funções de gatilho. O gatilho usado depende de se você deseja usar o thread atual, um thread em segundo plano ou um objeto de tarefa do .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 no thread da interface do usuário, não bloqueia o loop de eventos principal que processa ações do usuário, como pressionamentos de tecla e atividade do mouse, para que o aplicativo continue respondendo.

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. Ao chamar um método de forma assíncrona, em vez de uma associação comum let, 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. Nesse momento, 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, combinando com task { return! expr } |> Async.AwaitTask.

Fluxo de Controle

As expressões assíncronas podem incluir constructos de fluxo de controle, como for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. else e if .. then ... Elas podem, por sua vez, incluir construtos assíncronos 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 nesse caso.

Associações use e use!

Nas expressões assíncronas, as associações 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 do let!, você pode usar use! para executar associações assíncronas. A diferença entre let! e use! é a mesma diferença que há 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 permita.

Primitivos Assíncronos

Um método que executa uma única tarefa assíncrona e retorna o resultado é chamado de primitivo assíncrono, e ele é criado especificamente para uso com let!. Vários primitivos assíncronos são definidos na biblioteca principal do F#. Esses dois métodos para aplicativos Web são definidos no módulo FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse e WebClient.AsyncDownloadString. Ambos os primitivos baixam dados de uma página da Web, considerando uma URL. AsyncGetResponse produz um objeto System.Net.WebResponse e AsyncDownloadString 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 um método cujo corpo é uma expressão assíncrona.

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

Há um exemplo de uso de expressões assíncronas neste artigo. Há muitos outros na documentação dos 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 recebe o texto HTML retornado de uma solicitação da Web. A função fetchAsync contém um bloco de código assíncrono. Quando uma associação é feita para o resultado de um primitivo assíncrono, nesse caso AsyncDownloadString, let! é usada em vez de let.

Use a função Async.RunSynchronously para executar uma operação assíncrona e aguarde 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 que cada objeto de tarefa Async seja executado em paralelo e retorna um objeto Async que representa a computação paralela. Assim como ocorre em 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

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

Confira também