Асинхронные выражения
В этой статье описывается поддержка в F# для асинхронных выражений. Асинхронные выражения предоставляют один из способов асинхронного выполнения вычислений, то есть без блокировки выполнения других работ. Например, асинхронные вычисления можно использовать для записи приложений с пользовательскими интерфейсами, которые остаются адаптивными к пользователям, так как приложение выполняет другую работу. Модель программирования асинхронных рабочих процессов F# позволяет создавать функциональные программы при скрытии сведений о переходе потоков в библиотеке.
Асинхронный код также можно создать с помощью выражений задач , которые напрямую создают задачи .NET. Использование выражений задач предпочтительнее при взаимодействии с библиотеками .NET, которые создают или используют задачи .NET. При написании большинства асинхронного кода на F# асинхронные выражения F# предпочтительнее, так как они более краткие, более композиционные и избегают определенных предостережений, связанных с задачами .NET.
Синтаксис
async { expression }
Замечания
В предыдущем синтаксисе вычисления, представленные expression
, настроены для асинхронного выполнения, т. е. без блокировки текущего потока вычислений при выполнении асинхронных операций спящего режима, операций ввода-вывода и других асинхронных операций. Асинхронные вычисления часто запускаются в фоновом потоке, пока выполнение продолжается в текущем потоке. Тип выражения — Async<'T>
, где 'T
является типом, возвращаемым выражением при использовании ключевого слова return
.
Класс Async
предоставляет методы, поддерживающие несколько сценариев. Общий подход заключается в создании Async
объектов, представляющих вычисления или вычисления, которые требуется выполнять асинхронно, а затем запускать эти вычисления с помощью одной из функций триггера. Триггер, используемый, зависит от того, нужно ли использовать текущий поток, фоновый поток или объект задачи .NET. Например, чтобы запустить асинхронное вычисление в текущем потоке, можно использовать Async.StartImmediate
. При запуске асинхронного вычисления из потока пользовательского интерфейса не блокируется основной цикл событий, обрабатывающий действия пользователя, такие как нажатия клавиши и действие мыши, поэтому приложение остается адаптивным.
Асинхронная привязка с помощью let!
В асинхронном выражении некоторые выражения и операции синхронны, а некоторые — асинхронными. При асинхронном вызове метода вместо обычной привязки let
используется let!
. Эффект let!
заключается в том, чтобы позволить выполнению продолжаться на других вычислениях или потоках параллельно с выполнением текущих вычислений. После возвращения правой части привязки let!
выполнение остальной части асинхронного выражения возобновляется.
В следующем коде показано различие между let
и let!
. Строка кода, использующая let
, просто создает асинхронное вычисление в виде объекта, который можно запустить позже, используя, например, Async.StartImmediate
или Async.RunSynchronously
. Строка кода, использующая let!
, запускает вычисления и выполняет асинхронное ожидание: поток приостанавливается до тех пор, пока не станет доступен результат, после чего выполнение продолжается.
// 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!
можно использовать только напрямую для ожидания асинхронных вычислений F# Async<T>
. Вы можете ожидать другие асинхронные операции косвенно:
- Задачи .NET, Task<TResult> и не универсальные Taskпутем объединения с
Async.AwaitTask
- Задачи значений .NET, ValueTask<TResult> и неуниверсальные ValueTask, комбинируя с
.AsTask()
иAsync.AwaitTask
- Любой объект, следующий шаблону "GetAwaiter", указанному в F# RFC FS-1097, в комбинации с
task { return! expr } |> Async.AwaitTask
.
Поток управления
Асинхронные выражения могут включать конструкции потока управления, такие как for .. in .. do
, while .. do
, try .. with ..
, try .. finally ..
, if .. then .. else
и if .. then ..
. В свою очередь они могут включать дополнительные асинхронные конструкции, за исключением обработчиков with
и finally
, которые выполняются синхронно.
Асинхронные выражения F# не поддерживают асинхронные try .. finally ..
. Для этого случая можно использовать выражение задачи .
привязки use
и use!
В асинхронных выражениях привязки use
могут привязаться к значениям типа IDisposable. Для последнего операция очистки удаления выполняется асинхронно.
Помимо let!
можно использовать use!
для выполнения асинхронных привязок. Разница между let!
и use!
совпадает с разницей между let
и use
. Для use!
объект удаляется при закрытии текущей области. Обратите внимание, что в текущем выпуске F#, use!
не позволяет инициализировать значение как null, хотя use
это делает.
Асинхронные примитивы
Метод, который выполняет одну асинхронную задачу и возвращает результат, называется асинхронным примитивом, и такие методы предназначены специально для использования с let!
. В основной библиотеке F# определены несколько асинхронных примитивов. Два таких метода для веб-приложений определены в модуле FSharp.Control.WebExtensions
: WebRequest.AsyncGetResponse
и HttpClient.GetStringAsync (обернуты с помощью Async.AwaitTask
для совместимости с асинхронной моделью F#). Оба примитива загружают данные с веб-страницы, используя URL-адрес.
AsyncGetResponse
создает объект System.Net.WebResponse
и GetStringAsync
создает строку, представляющую HTML для веб-страницы.
В модуль FSharp.Control.CommonExtensions
включены несколько примитивов для асинхронных операций ввода-вывода. Эти методы расширения класса System.IO.Stream
— Stream.AsyncRead
и Stream.AsyncWrite
.
Вы также можете написать собственные асинхронные примитивы, определив функцию или метод, текст которого является асинхронным выражением.
Чтобы использовать асинхронные методы в .NET Framework, предназначенные для других асинхронных моделей с асинхронной моделью программирования F#, создайте функцию, которая возвращает объект F# Async
. Библиотека F# имеет функции, которые упрощают эту задачу.
Ниже приведен один из примеров использования асинхронных выражений; в документации к методам класса Async содержится много других примеров.
В этом примере показано, как использовать асинхронные выражения для параллельного выполнения кода.
В следующем примере кода функция fetchAsync
получает HTML-текст, возвращаемый из веб-запроса. Функция fetchAsync
содержит асинхронный блок кода. При создании привязки к результату асинхронного примитива в данном случае AsyncDownloadString
используется let!
вместо let
.
Функция Async.RunSynchronously
используется для выполнения асинхронной операции и ожидания его результата. Например, можно параллельно выполнять несколько асинхронных операций с помощью функции Async.Parallel
вместе с функцией Async.RunSynchronously
. Функция Async.Parallel
принимает список объектов Async
, настраивает код для каждого объекта задачи Async
для параллельного выполнения и возвращает объект Async
, представляющий параллельные вычисления. Как и для одной операции, вызовите Async.RunSynchronously
, чтобы запустить выполнение.
Функция runAll
запускает три асинхронных выражения параллельно и ожидает завершения работы.
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()