Асинхронные и задачи

FSharp.Core 11 добавляет функции для взаимодействия задач, преобразований результатов и ограниченного параллелизма. Используйте их с существующими асинхронными рабочими процессами и выражениями задач; Они не требуют прототипа среды выполнения асинхронного прототипа.

Операции ожидания .NET из асинхронной

Чтобы запустить операцию .NET с маркером отмены асинхронного рабочего процесса, передайте фабрику Async.StartTaskImmediateв:

open System.IO

let tryReadFile path =
    async {
        try
            let! contents =
                Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
            return Some contents
        with :? FileNotFoundException ->
            return None
    }

Файл считывает маркер отмены рабочего процесса. Отсутствующий файл возвращается None; возвращается Some contentsуспешное чтение. Рабочий процесс ожидает завершения, включая отмену и очистку.

Используйте, Async.Await если у вас уже есть значение, похожее на задачу. Оба API распространяют единственное базовое исключение неисправной задачи напрямую. Агрегат с несколькими внутренними исключениями остается агрегированным. Async.AwaitTask сохраняет существующее поведение упаковки исключений.

API Поддерживаемые фигуры аргументов и результатов
Async.Await Task<'T> -> Async<'T>; Task -> Async<unit>
Async.Await ValueTask<'T> -> Async<'T>; ValueTask -> Async<unit>
Async.Await Встроенная перегрузка, похожая GetAwaiter на задачу с помощью протокола.
Async.StartTaskImmediate (CancellationToken -> Task<'T>) -> Async<'T>; не универсальные возвращаемыеTask.Async<unit>
Async.StartTaskImmediate ValueTask<'T>Соответствующие ValueTaskперегрузки фабрики и задачи.
Task.startAsyncImmediate CancellationToken -> Async<'T> -> Task<'T>
Async.RunSynchronouslyImmediate Async<'T> * ?cancellationToken: CancellationToken -> 'T

Async.RunSynchronouslyImmediate запускается в вызывающем потоке, в отличие от Async.RunSynchronously потока переднего плана, и блоки для завершения. У него нет параметра времени ожидания. Предпочитайте асинхронную композицию в пользовательском интерфейсе и запрашивать потоки.

Предоставление асинхронного рабочего процесса в качестве задачи

Чтобы предложить приведенный выше рабочий процесс чтения файлов вызывающим абонентам на основе задач, примите маркер отмены и передайте его через Task.startAsyncImmediate:

let tryReadFileTask (path: string) (ct: CancellationToken) =
    tryReadFile path |> Task.startAsyncImmediate ct

Мост возвращает Task<string option> и распространяет отмену. Он начинается с вызывающего потока до первой приостановки; Более поздние продолжения не должны использовать этот поток.

Привязанная параллелизм

Значение Async<'T> описывает отложенную работу. Задача обычно представляет операцию, которая уже запущена. Чтобы ограничить использование ресурсов, опишите работу задачи в качестве фабрик, а не уже выполняемых задач:

open System.IO
open System.Threading

let readFiles (paths: seq<string>) (ct: CancellationToken) =
    paths
    |> Seq.map (fun path ->
        fun token -> File.ReadAllTextAsync(path, token))
    |> Task.parallelLimit 4 ct

Task.parallelLimit начинается по крайней мере положительное ограничение вычислений одновременно и возвращает результаты в порядке ввода. Порядок запуска и порядок завершения не определены. Для параллелизма больше одного, каждая фабрика получает связанный маркер, который также сигнализирует о сбое брата; для последовательного выполнения он получает предоставленный маркер напрямую.

При сбое вычислений запрашивается отмена для своих братьев и сестер. Операции должны наблюдать за их маркерами для сотрудничества. Если сбой нескольких вычислений, одно исключение распространяется; Не зависят от того, какой сбой выигрывает.

Function Фигура подписи
Task.parallelLimit int -> CancellationToken -> seq<CancellationToken -> Task<'T>> -> Task<'T[]>
Task.parallelDoLimit int -> CancellationToken -> seq<CancellationToken -> Task<unit>> -> Task<unit>
Task.sequential CancellationToken -> seq<CancellationToken -> Task<'T>> -> Task<'T[]>
Task.sequentialDo CancellationToken -> seq<CancellationToken -> Task<unit>> -> Task<unit>
Async.parallelLimit int -> seq<Async<'T>> -> Async<'T[]>
Async.parallelDoLimit int -> seq<Async<unit>> -> Async<unit>
Async.sequentialDo seq<Async<unit>> -> Async<unit>

Функции Async потоком маркера внешней отмены рабочего процесса. Существующий Async.Parallel (включая его параметр ограничения параллелизма) и Async.Sequential остается доступным. Варианты Do предоставляют результат, unit если требуется только завершение. Ограничение параллелизма не является ограничением скорости: быстрые операции по-прежнему могут создавать много запросов в секунду.

Преобразование результатов и восстановление после сбоев

Модули Microsoft.FSharp.Control.Asyncи ValueTask модули Taskпредоставляют эти квалифицированные функции. M<'T> в столбце фигуры соответствует соответствующему типу вычислений, а не сокращению типа F#.

Квалифицированные функции Shape
Async.result, Task.result, ValueTask.result 'T -> M<'T>
Async.map, Task.map, ValueTask.map ('T -> 'U) -> M<'T> -> M<'U>
Async.bind, Task.bind, ValueTask.bind ('T -> M<'U>) -> M<'T> -> M<'U>
Async.ignore<'T>, Task.ignore<'T>, ValueTask.ignore<'T> M<'T> -> M<unit>; обязательный аргумент явного типа ввода.
Async.catchWith, Task.catchWith, ValueTask.catchWith (exn -> 'T) -> M<'T> -> M<'T>
Async.catch, Task.catch, ValueTask.catch M<'T> -> M<Result<'T, exn>>
Async.empty, Task.empty, ValueTask.empty M<unit>
Task.ofValueTask ValueTask<'T> -> Task<'T>
ValueTask.ofTask Task<'T> -> ValueTask<'T>

Например, сопоставляйте содержимое файла с длиной, а затем представляет сбой чтения в виде Result:

open System.IO
open System.Threading

let fileLength (path: string) (ct: CancellationToken) =
    File.ReadAllTextAsync(path, ct)
    |> Task.map String.length
    |> Task.catch

Файл, Report 1 содержащий данные Ok 8. Отсутствующий файл предоставляет Error содержащий объект FileNotFoundException. Чтобы отменить успешное значение, а не преобразовать его, укажите тип входных данных при вызове ignore:

let completion: System.Threading.Tasks.Task<unit> =
    Task.result 42 |> Task.ignore<int>

catchWith предоставляет значение замены для сбоя без отмены; он не выполняет еще одно асинхронное вычисление. catch возвращает Ok или Error. Отмена не преобразуется в значение ошибки или передается обработчику восстановления. Task.empty Task<unit>— это не универсальный Task.CompletedTaskобъект.

В отличие от нижнего регистра Async.catch, существующие Async.Catch возвращается Choice. Восстановление обрабатывает сбои предоставленных вычислений, а не синхронные исключения, создаваемые перед вызовом. Асинхронная синхронизация остается отложенной; задачи остаются готовыми.

Доступность ValueTask и библиотеки

Для API ValueTask требуется netstandard2.1 или net10.0 сборка FSharp.Core 11. См. раздел "Основной целевой объект".

Обратите внимание на контракт ValueTask с одним потреблением: потребляйте каждое значение один раз, а не через отдельные ожидания или конвейеры. Для повторяющихся ожиданий вызов Task.ofValueTask один раз и повторное использование только результирующей задачи.

См. также