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

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

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

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

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

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

В следующем примере кода функция 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()

См. также