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

В этой статье описывается поддержка в 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.RunSynchronouslyAsync.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-адрес. AsyncGetResponseSystem.Net.WebResponse создает объект и AsyncDownloadString создает строку, представляющую HTML для веб-страницы.

В модуль включены FSharp.Control.CommonExtensions несколько примитивов для асинхронных операций ввода-вывода. Эти методы System.IO.Stream расширения класса и Stream.AsyncReadStream.AsyncWrite.

Вы также можете написать собственные асинхронные примитивы, определив функцию или метод, текст которого является асинхронным выражением.

Чтобы использовать асинхронные методы в платформа .NET Framework, предназначенных для других асинхронных моделей с асинхронной моделью программирования F#, создайте функцию, которая возвращает объект F#Async. Библиотека F# имеет функции, которые упрощают эту задачу.

Ниже приведен один из примеров использования асинхронных выражений; В документации по методам класса Async есть много других пользователей.

В этом примере показано, как использовать асинхронные выражения для параллельного выполнения кода.

В следующем примере кода функция fetchAsync получает HTML-текст, возвращаемый из веб-запроса. Функция fetchAsync содержит асинхронный блок кода. При выполнении привязки к результату асинхронного примитива в данном случае AsyncDownloadStringlet! используется вместо 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()

См. также