如何实施 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 docs 的 TaskStatus
支票。我对raise antecedent.Exception
感到最不确定,但我想不出其他方法来处理它。
作为背景,是的,我知道 Async
,但我当前的堆栈使用 Entity Framework 和 Blazor,所以我有一个使用 .ToListAsync()
之类的后端和一个前端以 C# 结尾,所以我宁愿不处理从 Task
到 Async
的转换,然后再返回。
在继续中再次抛出异常将导致错误的堆栈跟踪。
这是来自 '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
概念背后的接口实施您的解决方案,即 INotifyCompletion
和 ICriticalNotifyCompletion
。此外,要正确实施 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.
我是否为 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 docs 的 TaskStatus
支票。我对raise antecedent.Exception
感到最不确定,但我想不出其他方法来处理它。
作为背景,是的,我知道 Async
,但我当前的堆栈使用 Entity Framework 和 Blazor,所以我有一个使用 .ToListAsync()
之类的后端和一个前端以 C# 结尾,所以我宁愿不处理从 Task
到 Async
的转换,然后再返回。
在继续中再次抛出异常将导致错误的堆栈跟踪。
这是来自 '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
概念背后的接口实施您的解决方案,即 INotifyCompletion
和 ICriticalNotifyCompletion
。此外,要正确实施 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.