Асинхронные выражения
В этой статье описывается поддержка в 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.RunSynchronously
Async.StartImmediate
. Строка кода, которая использует 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!
можно использовать только для ожидания асинхронных вычислений Async<T>
F# напрямую. Вы можете ожидать другие асинхронные операции косвенно:
- Задачи .NET и не универсальныеTask, Task<TResult> объединение с
Async.AwaitTask
- Задачи значений .NET и не универсальныеValueTask, ValueTask<TResult> сочетая и
.AsTask()
Async.AwaitTask
- Любой объект после шаблона GetAwaiter, указанный в F# RFC FS-1097, путем
task { return! expr } |> Async.AwaitTask
объединения с .
Поток управления
Асинхронные выражения могут включать конструкции потока управления, такие как for .. in .. do
, , while .. do
, try .. finally ..
try .. with ..
, if .. then .. else
и if .. then ..
. Они могут, в свою очередь, включать дополнительные асинхронные конструкции, за исключением with
обработчиков, finally
которые выполняются синхронно.
Асинхронные выражения F# не поддерживают асинхронные try .. finally ..
. Для этого случая можно использовать выражение задачи.
use
и use!
привязки
В асинхронных выражениях привязки use
могут привязаться к значениям типа IDisposable. Для последнего операция очистки удаления выполняется асинхронно.
Кроме того let!
, можно использовать use!
для асинхронных привязок. Разница между и совпадает с разницей между let!
let
и use
.use!
Для use!
этого объект удаляется в конце текущего область. Обратите внимание, что в текущем выпуске F# use!
значение не позволяет инициализировать значение null, даже если use
это делает.
Асинхронные примитивы
Метод, выполняющий одну асинхронную задачу и возвращающий результат, называется асинхронным примитивом, и они предназначены специально для использования.let!
В основной библиотеке F# определены несколько асинхронных примитивов. В модуле FSharp.Control.WebExtensions
определены два таких метода для веб-приложений: WebRequest.AsyncGetResponse
и WebClient.AsyncDownloadString
. Оба примитива загружают данные с веб-страницы, используя URL-адрес. AsyncGetResponse
System.Net.WebResponse
создает объект и AsyncDownloadString
создает строку, представляющую 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()