Асинхронные рабочие потоки (F#)
В этом разделе описаны особенности языка F#, обеспечивающие возможность выполнения асинхронных вычислений, т. е. без блокировки других операций. Например, асинхронные вычисления могут использоваться для создания приложений с интерфейсами пользователя, обеспечивающими реакцию на действия пользователя во время выполнения приложением других операций.
async { expression }
Заметки
В предыдущем синтаксисе вычисления, представленные параметром expression, запускаются асинхронно, то есть не блокируют текущий поток вычислений, когда выполняются асинхронные операции перевода в спящий режим, ввода/вывода и другие асинхронные операции. Асинхронные вычисления часто запускаются в фоновом потоке, в то время как выполнение продолжается в текущем потоке. Выражение имеет тип Async<'a>, где 'a — это тип, возвращаемый выражением при использовании ключевого слова return. Код в таком выражении называется асинхронным блоком или блоком async.
Существует несколько способов асинхронного программирования, и класс Async предоставляет методы, поддерживающие несколько сценариев. Общий подход заключается в создании объектов Async, представляющих вычисление или вычисления, которые требуется выполнять асинхронно, с последующим запуском этих вычислений с помощью одной из запускающих функций. Различные запускающие функции обеспечивают различные способы выполнения асинхронных вычислений, и выбор зависит от того, требуется ли использовать текущий поток, фоновый поток или объект задачи платформы .NET Framework, а также от того, имеются ли последующие функции, которые должны выполнятся после завершения вычисления. Например, чтобы запустить асинхронное вычисление в текущем потоке, можно использовать функцию 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! для выполнения асинхронных привязок можно использовать use!. Разница между let! и use! такая же, как между let и use. В случае use! объект уничтожается при закрытии текущей области. Обратите внимание, что в текущей версии языка F # use! не позволяет инициализировать значение null, даже если это возможно с помощью use.
Асинхронные примитивы
Метод, выполняющий одну асинхронную задачу и возвращающий результат, называется асинхронным примитивом, и такие методы предназначены специально для использования с привязкой let!. В основной библиотеке языка F# определено несколько асинхронных примитивов. Два таких метода для веб-приложений определены в модуле Microsoft.FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse и WebClient.AsyncDownloadString. Оба примитива получают URL-адрес и загружают данные с веб-страницы. AsyncGetResponse создает объект WebResponse, а AsyncDownloadString создает строку, представляющую код HTML для веб-страницы.
Несколько примитивов для асинхронных операций ввода-вывода содержатся в модуле Microsoft.FSharp.Control.CommonExtensions. Это методы расширения класса Stream: Stream.AsyncRead и Stream.AsyncWrite.
Дополнительные асинхронные примитивы входят в состав пакета PowerPack языка F#. Можно также написать собственные асинхронные примитивы, определив функцию, все тело которой расположено в асинхронном блоке.
Чтобы использовать с моделью асинхронного программирования F# асинхронные методы платформы .NET Framework, предназначенные для других асинхронных моделей, следует создать функцию, возвращающую объект Async языка F#. Библиотека языка 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
let urlList = [ "Microsoft.com", "https://www.microsoft.com/"
"MSDN", "https://msdn.microsoft.com/"
"Bing", "https://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()
См. также
Ссылки
Другие ресурсы
Журнал изменений
Дата |
Журнал |
Причина |
---|---|---|
Октябрь 2010 |
Уточнено, что не все асинхронные операции выполняются в фоновом потоке. |
Улучшение информации. |