Асинхронные выражения
В этой статье описывается поддержка в F# для асинхронных выражений. Асинхронные выражения обеспечивают один из способов асинхронного выполнения вычислений, то есть без блокировки выполнения других работ. Например, асинхронные вычисления можно использовать для записи приложений с пользовательскими интерфейсами, которые остаются адаптивными к пользователям, так как приложение выполняет другую работу.
Асинхронный код также можно создавать с помощью выражений задач, которые создают задачи .NET напрямую. Использование выражений задач рекомендуется использовать при активном взаимодействии с библиотеками .NET, которые создают или используют задачи .NET. При написании большинства асинхронных кодов в F#, асинхронные выражения F# предпочтительнее, так как они более кратки, более композиционные и избегают определенных предостережений, связанных с задачами .NET.
Синтаксис
async { expression }
Remarks
В предыдущем синтаксисе вычисление, представленное 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!
можно использовать только для непосредственного ожидания асинхронных вычислений Async<T>
F#. Вы можете ожидать другие виды асинхронных операций косвенно:
- Задачи Task<TResult> .NET и неуниверсические Taskзадачи, объединяя их с
Async.AwaitTask
- Задачи ValueTask<TResult> значений .NET и неуниверсическиеValueTask, объединяя их и
.AsTask()
Async.AwaitTask
- Любой объект, следующий за шаблоном GetAwaiter, указанным в F# RFC FS-1097, путем объединения с
task { return! expr } |> Async.AwaitTask
.
Поток управления
Асинхронные выражения могут включать конструкции потока управления, такие как for .. in .. do
, , while .. do
, try .. with ..
, if .. then .. else
try .. finally ..
и if .. then ..
. Они, в свою очередь, могут включать дополнительные асинхронные конструкции, за исключением with
обработчиков и finally
которые выполняются синхронно.
Асинхронные выражения F# не поддерживают асинхронные try .. finally ..
. Для этого случая можно использовать выражение задачи .
use
и use!
привязки
В асинхронных выражениях привязки use
могут привязаться к значениям типа IDisposable. В последнем случае операция очистки удаления выполняется асинхронно.
Кроме того let!
, можно использовать для use!
выполнения асинхронных привязок. Разница между и use!
совпадает с let!
разницей между let
и use
. Для use!
этого объект удаляется в конце текущей области. Обратите внимание, use!
что в текущем выпуске F# не позволяет инициализировать значение 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# имеет функции, которые упрощают эту задачу.
Здесь приведен один из примеров использования асинхронных выражений; В документации по методам асинхронного класса есть много других.
В этом примере показано, как использовать асинхронные выражения для параллельного выполнения кода.
В следующем примере кода функция 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
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()