如何实施 Task.Map

How to implement Task.Map

我是否为 Task 正确实施了 map

let map continuation (t: Task<'A>) =
    t.ContinueWith(fun (antecedent: Task<'A>) ->
        if  antecedent.Status <> TaskStatus.Canceled &&
            antecedent.Status <> TaskStatus.Faulted then
            continuation antecedent.Result
        else
            raise antecedent.Exception // must I?
    )

我收到了 the docsTaskStatus 支票。我对raise antecedent.Exception感到最不确定,但我想不出其他方法来处理它。


作为背景,是的,我知道 Async,但我当前的堆栈使用 Entity Framework 和 Blazor,所以我有一个使用 .ToListAsync() 之类的后端和一个前端以 C# 结尾,所以我宁愿不处理从 TaskAsync 的转换,然后再返回。

在继续中再次抛出异常将导致错误的堆栈跟踪。 这是来自 'A -> 'B 的映射,因此最好将其明确布局。

let rec map (continuation: 'A -> 'B) (t: Task<'A>) =
    let rec map_resolved (task: Task<'A>) = 
        match task.Status with 
        | TaskStatus.RanToCompletion -> Task.FromResult(continuation task.Result)
        | TaskStatus.Faulted         -> Task.FromException<'B>(task.Exception)
        | TaskStatus.Canceled        -> Task.FromCanceled<'B>(CancellationToken.None)
        | _                          -> task.ContinueWith(map_resolved).Unwrap()
    map_resolved t

我建议根据 TPL 中 awaitable 概念背后的接口实施您的解决方案,即 INotifyCompletionICriticalNotifyCompletion。此外,要正确实施 map,您应该真正按照 bind 来实施。这在 F# 中已经有一些现有的解决方案,例如 TaskBuilder 库。就个人而言,多年来我一直在图书馆中使用以下内容,没有任何问题:

open System.Runtime.CompilerServices
open System.Threading.Tasks

type TaskStep<'result> =
| Value of 'result
| AsyncValue of 'result Task
| Continuation of ICriticalNotifyCompletion * (unit -> 'result TaskStep)
and StateMachine<'a>(firstStep) as this =
    let methodBuilder = AsyncTaskMethodBuilder<'a Task>()
    let mutable continuation = fun () -> firstStep
    let nextAwaitable() =
        try
            match continuation() with
            | Value r ->
                methodBuilder.SetResult(Task.FromResult(r))
                null
            | AsyncValue t ->
                methodBuilder.SetResult(t)
                null
            | Continuation (await, next) ->
                continuation <- next
                await
        with
        | exn ->
            methodBuilder.SetException(exn)
            null
    let mutable self = this

    member __.Run() =
        methodBuilder.Start(&self)
        methodBuilder.Task

    interface IAsyncStateMachine with
        member __.MoveNext() =
            let mutable await = nextAwaitable()
            if not (isNull await) then
                methodBuilder.AwaitUnsafeOnCompleted(&await, &self)    
        member __.SetStateMachine(_) = 
            () 

type Binder<'out> =
    static member inline GenericAwait< ^abl, ^awt, ^inp
                                        when ^abl : (member GetAwaiter : unit -> ^awt)
                                        and ^awt :> ICriticalNotifyCompletion 
                                        and ^awt : (member get_IsCompleted : unit -> bool)
                                        and ^awt : (member GetResult : unit -> ^inp) >
        (abl : ^abl, continuation : ^inp -> 'out TaskStep) : 'out TaskStep =
            let awt = (^abl : (member GetAwaiter : unit -> ^awt)(abl))
            if (^awt : (member get_IsCompleted : unit -> bool)(awt)) 
            then continuation (^awt : (member GetResult : unit -> ^inp)(awt))
            else Continuation (awt, fun () -> continuation (^awt : (member GetResult : unit -> ^inp)(awt)))

module TaskStep =
    let inline bind f step : TaskStep<'a> =
        Binder<'a>.GenericAwait(step, f)

    let inline toTask (step: TaskStep<'a>) =
        try
            match step with
            | Value x -> Task.FromResult(x)
            | AsyncValue t -> t
            | Continuation _ as step -> StateMachine<'a>(step).Run().Unwrap()
        with
        | exn ->
            let src = new TaskCompletionSource<_>()
            src.SetException(exn)
            src.Task

module Task =
    let inline bind f task : Task<'a> =
        TaskStep.bind f task |> TaskStep.toTask

    let inline map f task : Task<'b> =
        bind (f >> Value) task

FsToolkit.ErrorHandling implements it here. I'll paste the current version below as it's quite short. It uses the TaskBuilder library亚伦提到。

module Task =
  let singleton value = value |> Task.FromResult

  let bind (f : 'a -> Task<'b>) (x : Task<'a>) = task {
      let! x = x
      return! f x
  }

  let map f x = x |> bind (f >> singleton)

另外,FSharpPlus has an independent implementation of Task.map here.