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 }
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. 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 return
palavra-chave é usada.
A Async
classe 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 comum let
, você usa let!
. O efeito de let!
é permitir que a execução continue em outros cálculos ou threads à medida que a computação está sendo executada. Depois que let!
o lado direito da associação retorna, 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 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 para aguardar cálculos assíncronos Async<T>
F# diretamente. Você pode aguardar outros tipos de operações assíncronas indiretamente:
- Tarefas Task<TResult> .NET e as não genéricas Task, combinando com
Async.AwaitTask
- Tarefas ValueTask<TResult> de valor .NET e as tarefas não genéricas ValueTask, combinando com
.AsTask()
eAsync.AwaitTask
- Qualquer objeto que siga o padrão "GetAwaiter" especificado em F# RFC FS-1097, combinando com
task { return! expr } |> Async.AwaitTask
.
Fluxo de Controlo
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 with
, com exceção dos manipuladores e finally
, que são executados de forma síncrona.
As expressões assíncronas F# não suportam assíncronas.try .. finally ..
Você pode usar uma expressão de tarefa para este caso.
use
e use!
vinculações
Dentro de expressões assíncronas, use
as associações podem se vincular a valores do tipo IDisposable. Para este último, a operação de limpeza do 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 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 use
que isso aconteç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!
o . 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 WebClient.AsyncDownloadString
. Ambos os primitivos baixam dados de uma página da Web, dada uma URL. AsyncGetResponse
produz um System.Net.WebResponse
objeto e AsyncDownloadString
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 FSharp.Control.CommonExtensions
módulo. Estes métodos de extensão da System.IO.Stream
classe 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 da 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 fetchAsync
função 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
.
Você usa 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 Async.Parallel
função junto com a Async.RunSynchronously
função. A Async.Parallel
função usa uma lista dos objetos, configura o código para que cada Async
objeto de Async
tarefa seja executado em paralelo e retorna um Async
objeto que representa a computação paralela. Assim como para uma única operação, você chama Async.RunSynchronously
para iniciar a execução.
A runAll
função 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
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()