异步和任务函数

FSharp.Core 11 为任务互操作、结果转换和有限并发添加函数。 将它们与现有 异步工作流 和 任务表达式配合使用;它们不需要运行时异步原型。

从异步执行 await .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、 ValueTask<'T>类似任务的工厂重载。
Task.startAsyncImmediate CancellationToken -> Async<'T> -> Task<'T>
Async.RunSynchronouslyImmediate Async<'T> * ?cancellationToken: CancellationToken -> 'T

Async.RunSynchronouslyImmediate 在调用线程上启动,与 Async.RunSynchronously 前台线程不同,以及用于完成的块。 它没有超时参数。 首选 UI 和请求线程上的异步组合。

将异步工作流公开为任务

若要向基于任务的调用方提供上述文件读取工作流,请接受取消令牌并通过 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 从并发计算的正限制开始,并按输入顺序返回结果。 启动顺序和完成顺序不指定。 对于大于 1 的并发性,每个工厂都会收到一个链接令牌,该令牌也表示同级故障;对于顺序执行,它直接接收提供的令牌。

发生一个计算错误时,会为其同级请求取消。 操作必须观察其令牌才能合作。 如果多个计算失败,则会传播一个异常;不依赖于哪个失败获胜。

函数 签名形状
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.AsyncTaskValueTask模块公开这些限定的函数。 M<'T> 在形状列中表示相应的计算类型,而不是 F# 类型缩写。

限定的函数 形状
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的文件。 缺少的文件提供包含的一个 ErrorFileNotFoundException。 若要放弃成功的值而不是转换它,请在调用 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 和库可用性

ValueTask API 需要 netstandard2.1 FSharp.Core 11 或 net10.0 生成。 请参阅 核心目标选择。

观察 ValueTask 单消耗协定:使用每个值一次,而不是通过单独的等待或管道。 对于重复的等待,请调用 Task.ofValueTask 一次,只重复使用生成的任务。

另请参阅