Поделиться через


Асинхронные выражения

В этой статье описывается поддержка в 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.StreamStream.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()

См. также