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()
eAsync.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 .. else
e 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()