From 15d9da9121ac66687d2de66350f94a6cbb85c61a Mon Sep 17 00:00:00 2001 From: gusty <1261319+gusty@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:24:20 +0100 Subject: [PATCH 1/2] Avoid F# built-in task CEs --- src/FSharpPlus/Extensions/Task.fs | 62 +++++++++++++++++-------- src/FSharpPlus/Extensions/ValueTask.fs | 63 ++++++++++++++++++-------- 2 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/FSharpPlus/Extensions/Task.fs b/src/FSharpPlus/Extensions/Task.fs index 36406ba22..9b1a89fbc 100644 --- a/src/FSharpPlus/Extensions/Task.fs +++ b/src/FSharpPlus/Extensions/Task.fs @@ -60,10 +60,15 @@ module Task = let map (mapper: 'T -> 'U) (source: Task<'T>) : Task<'U> = let source = nullArgCheck (nameof source) source - backgroundTask { - let! r = source - return mapper r - } + if source.IsCompleted then + match source with + | Succeeded r -> try result (mapper r) with e -> Task.FromException<_> e + | Faulted exn -> FromExceptions exn + | Canceled -> canceled + else + let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + source |> continueTask tcs (fun r -> try tcs.SetResult (mapper r) with e -> tcs.SetException e) + tcs.Task /// Creates a task workflow from two workflows 'task1' and 'task2', mapping its results with 'mapper'. /// Workflows are run in sequence. @@ -293,19 +298,13 @@ module Task = let join (source: Task>) : Task<'T> = let source = nullArgCheck (nameof source) source - backgroundTask { - let! inner = source - return! inner - } + source.Unwrap() /// Creates a task workflow from 'source' workflow, mapping and flattening its result with 'f'. let bind (f: 'T -> Task<'U>) (source: Task<'T>) : Task<'U> = let source = nullArgCheck (nameof source) source - backgroundTask { - let! r = source - return! f r - } + source |> Unchecked.nonNull |> map f |> join /// Creates a task that ignores the result of the source task. /// The source Task. @@ -327,14 +326,41 @@ module Task = tcs.Task [] - let tryWith (body: unit -> Task<'T>) (compensation: exn -> Task<'T>) : Task<'T> = backgroundTask { - try return! body () - with e -> return! compensation e } + let rec tryWith (body: unit -> Task<'T>) (compensation: exn -> Task<'T>) : Task<'T> = + let runCompensation exn = + try compensation exn + with e -> Task.FromException<'T> e + let unwrapException (agg: AggregateException) = + if agg.InnerExceptions.Count = 1 then agg.InnerExceptions.[0] + else agg :> Exception + try Ok (body ()) with e -> Error e + |> function + | Ok task -> + if task.IsCompleted then + match task with + | Succeeded _ -> task + | Faulted aex -> runCompensation (unwrapException aex) + | Canceled -> canceled + else + task.ContinueWith(fun (x: Task<'T>) -> tryWith (fun () -> x) compensation).Unwrap () + | Error exn -> runCompensation exn [] - let tryFinally (body: unit -> Task<'T>) (compensation : unit -> unit) : Task<'T> = backgroundTask { - try return! body () - finally compensation () } + let rec tryFinally (body: unit -> Task<'T>) (compensation : unit -> unit) : Task<'T> = + let task = + try body () + with _ -> + try + compensation () + reraise () + with e -> Task.FromException<'T> e + if task.IsCompleted then + try + compensation () + task + with e -> Task.FromException<'T> e + else + task.ContinueWith(fun (x: Task<'T>) -> tryFinally (fun () -> x) compensation).Unwrap () /// Used to de-sugar use .. blocks in Computation Expressions. let using (disp: 'T when 'T :> IDisposable) (body: 'T -> Task<'U>) = diff --git a/src/FSharpPlus/Extensions/ValueTask.fs b/src/FSharpPlus/Extensions/ValueTask.fs index d985dae50..289223ea0 100644 --- a/src/FSharpPlus/Extensions/ValueTask.fs +++ b/src/FSharpPlus/Extensions/ValueTask.fs @@ -63,10 +63,15 @@ module ValueTask = /// The source ValueTask workflow. /// The resulting ValueTask workflow. let map (mapper: 'T -> 'U) (source: ValueTask<'T>) : ValueTask<'U> = - backgroundTask { - let! r = source - return mapper r - } |> ValueTask<'U> + if source.IsCompleted then + match source with + | Succeeded r -> try result (mapper r) with e -> ValueTask.FromException<_> e + | Faulted exn -> FromExceptions exn + | Canceled -> canceled + else + let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + source |> continueTask tcs (fun r -> try tcs.SetResult (mapper r) with e -> tcs.SetException e) + tcs.Task |> ValueTask<'U> /// Creates a ValueTask workflow from two workflows 'x' and 'y', mapping its results with 'f'. @@ -271,17 +276,11 @@ module ValueTask = /// Flattens two nested ValueTask into one. let join (source: ValueTask>) : ValueTask<'T> = - backgroundTask { - let! inner = source - return! inner - } |> ValueTask<'T> + (source |> map (fun x -> x.AsTask())).AsTask().Unwrap () |> ValueTask<'T> /// Creates a ValueTask workflow from 'source' workflow, mapping and flattening its result with 'f'. let bind (f: 'T -> ValueTask<'U>) (source: ValueTask<'T>) : ValueTask<'U> = - backgroundTask { - let! r = source - return! f r - } |> ValueTask<'U> + source |> map f |> join /// Creates a ValueTask that ignores the result of the source ValueTask. /// The source ValueTask. @@ -302,17 +301,41 @@ module ValueTask = /// Used to de-sugar try .. with .. blocks in Computation Expressions. let inline tryWith ([]compensation: exn -> ValueTask<'T>) ([]body: unit -> ValueTask<'T>) : ValueTask<'T> = - backgroundTask { - try return! body () - with e -> return! compensation e - } |> ValueTask<'T> + let runCompensation exn = + try compensation exn + with e -> ValueTask.FromException<'T> e + let unwrapException (agg: AggregateException) = + if agg.InnerExceptions.Count = 1 then agg.InnerExceptions.[0] + else agg :> Exception + try Ok (body ()) with e -> Error e + |> function + | Ok task -> + if task.IsCompleted then + match task with + | Succeeded _ -> task + | Faulted aex -> runCompensation (unwrapException aex) + | Canceled -> canceled + else + task.AsTask().ContinueWith(fun (x: Task<'T>) -> Task.tryWith (compensation >> fun x -> x.AsTask()) (fun () -> x)).Unwrap () |> ValueTask<'T> + | Error exn -> runCompensation exn + /// Used to de-sugar try .. finally .. blocks in Computation Expressions. let inline tryFinally ([]compensation : unit -> unit) ([]body: unit -> ValueTask<'T>) : ValueTask<'T> = - backgroundTask { - try return! body () - finally compensation () - } |> ValueTask<'T> + let task = + try body () + with _ -> + try + compensation () + reraise () + with e -> ValueTask.FromException<'T> e + if task.IsCompleted then + try + compensation () + task + with e -> ValueTask.FromException<'T> e + else + task.AsTask().ContinueWith(fun (x: Task<'T>) -> Task.tryFinally compensation (fun () -> x)).Unwrap () |> ValueTask<'T> /// Used to de-sugar use .. blocks in Computation Expressions. let inline using (disp: 'T when 'T :> IDisposable) ([]body: 'T -> ValueTask<'U>) = From 6b43f5126700009aa49193fd730fa56b6eeaa972 Mon Sep 17 00:00:00 2001 From: gusty <1261319+gusty@users.noreply.github.com> Date: Sat, 10 Jan 2026 09:23:54 +0100 Subject: [PATCH 2/2] Reorganize code for multitargeting --- src/FSharpPlus/Extensions/Task.fs | 107 +++++++++++++++---------- src/FSharpPlus/Extensions/ValueTask.fs | 89 ++++++++++++-------- 2 files changed, 118 insertions(+), 78 deletions(-) diff --git a/src/FSharpPlus/Extensions/Task.fs b/src/FSharpPlus/Extensions/Task.fs index 9b1a89fbc..ccf3143d8 100644 --- a/src/FSharpPlus/Extensions/Task.fs +++ b/src/FSharpPlus/Extensions/Task.fs @@ -14,10 +14,11 @@ module Task = /// Active pattern to match the state of a completed Task let inline internal (|Succeeded|Canceled|Faulted|) (t: Task<'a>) = - if t.IsCompletedSuccessfully then Succeeded t.Result - elif t.IsFaulted then Faulted (Unchecked.nonNull t.Exception) - elif t.IsCanceled then Canceled - else invalidOp "Internal error: The task is not yet completed." + match t.Status with + | TaskStatus.RanToCompletion -> Succeeded t.Result + | TaskStatus.Faulted -> Faulted (Unchecked.nonNull t.Exception) + | TaskStatus.Canceled -> Canceled + | _ -> invalidOp (sprintf "Internal error: The task is not yet in a final state. State = TaskStatus.%A" t.Status) let inline internal continueTask (tcs: TaskCompletionSource<'Result>) (k: 't -> unit) (x: Task<'t>) = let f = function @@ -26,32 +27,56 @@ module Task = | Canceled -> tcs.SetCanceled () x.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted (fun () -> f x) + #if NET5_0_OR_GREATER + let [] private tcsOptions = TaskCreationOptions.RunContinuationsAsynchronously + #else + let private tcsOptions = () + #endif /// Creates a Task that's completed successfully with the specified value. /// /// A Task that is completed successfully with the specified value. let result (value: 'T) : Task<'T> = Task.FromResult value - + + + /// Creates a Task that's completed unsuccessfully with the specified exception. + /// The exception to be raised. + /// A Task that is completed unsuccessfully with the specified exception. + let raise<'T> (exn: exn) : Task<'T> = + #if NET5_0_OR_GREATER + Task.FromException<'T> exn + #else + let tcs = TaskCompletionSource<'T> tcsOptions + tcs.SetException exn + tcs.Task + #endif + /// Creates a Task that's completed unsuccessfully with the specified exceptions. - /// The AggregateException to be raised. + /// The AggregateException to be raised. /// A Task that is completed unsuccessfully with the specified exceptions. /// /// Prefer this function to handle AggregateExceptions over Task.FromException as it handles them correctly. /// let inline internal FromExceptions<'T> (aex: AggregateException) : Task<'T> = + #if NET5_0_OR_GREATER match aex with | agg when agg.InnerExceptions.Count = 1 -> Task.FromException<'T> agg.InnerExceptions[0] - | agg -> - let tcs = TaskCompletionSource<'T> () - tcs.SetException agg.InnerExceptions + | _ -> + #endif + let tcs = TaskCompletionSource<'T> tcsOptions + tcs.SetException aex.InnerExceptions tcs.Task - let private cancellationTokenSingleton = CancellationToken true - /// Creates a Task that's canceled. /// A Task that's canceled. - let canceled<'T> : Task<'T> = Task.FromCanceled<'T> cancellationTokenSingleton - + let canceled<'T> : Task<'T> = + #if NET5_0_OR_GREATER + Task.FromCanceled<'T> (CancellationToken true) + #else + let tcs = TaskCompletionSource<'T> tcsOptions + tcs.SetCanceled () + tcs.Task + #endif /// Creates a task workflow from 'source' workflow, mapping its result with 'mapper'. /// The mapping function. @@ -62,11 +87,11 @@ module Task = if source.IsCompleted then match source with - | Succeeded r -> try result (mapper r) with e -> Task.FromException<_> e + | Succeeded r -> try result (mapper r) with e -> raise e | Faulted exn -> FromExceptions exn | Canceled -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions source |> continueTask tcs (fun r -> try tcs.SetResult (mapper r) with e -> tcs.SetException e) tcs.Task @@ -81,13 +106,13 @@ module Task = if task1.IsCompleted && task2.IsCompleted then match task1, task2 with - | Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> Task.FromException<_> e + | Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> raise e | Succeeded _ , Faulted exn -> FromExceptions exn | Succeeded _ , Canceled -> canceled | Faulted exn , _ -> FromExceptions exn | Canceled , _ -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions match task1.Status, task2.Status with | TaskStatus.Canceled, _ -> tcs.SetCanceled () @@ -112,7 +137,7 @@ module Task = if task1.IsCompleted && task2.IsCompleted && task3.IsCompleted then match task1, task2, task3 with - | Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> Task.FromException<_> e + | Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> raise e | Faulted exn , _ , _ -> FromExceptions exn | Canceled , _ , _ -> canceled | _ , Faulted exn , _ -> FromExceptions exn @@ -120,7 +145,7 @@ module Task = | _ , _ , Faulted exn -> FromExceptions exn | _ , _ , Canceled -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions match task1.Status, task2.Status, task3.Status with | TaskStatus.Canceled, _ , _ -> tcs.SetCanceled () | TaskStatus.Faulted , _ , _ -> tcs.SetException (Unchecked.nonNull task1.Exception).InnerExceptions @@ -147,9 +172,9 @@ module Task = let task2 = nullArgCheck (nameof task2) task2 if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion then - try result (mapper task1.Result task2.Result) with e -> Task.FromException<'U> e + try result (mapper task1.Result task2.Result) with e -> raise e else - let tcs = TaskCompletionSource<_> () + let tcs = TaskCompletionSource<_> tcsOptions let r1 = ref Unchecked.defaultof<_> let r2 = ref Unchecked.defaultof<_> let mutable cancelled = false @@ -195,9 +220,9 @@ module Task = if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion && task3.Status = TaskStatus.RanToCompletion then try result (mapper task1.Result task2.Result task3.Result) - with e -> Task.FromException<'U> e + with e -> raise e else - let tcs = TaskCompletionSource<_> () + let tcs = TaskCompletionSource<_> tcsOptions let r1 = ref Unchecked.defaultof<_> let r2 = ref Unchecked.defaultof<_> let r3 = ref Unchecked.defaultof<_> @@ -241,13 +266,13 @@ module Task = if f.IsCompleted && x.IsCompleted then match f, x with - | Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> Task.FromException<_> e + | Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> raise e | Succeeded _ , Faulted exn -> FromExceptions exn | Succeeded _ , Canceled -> canceled | Faulted exn , _ -> FromExceptions exn | Canceled , _ -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions match f.Status, x.Status with | TaskStatus.Canceled, _ -> tcs.SetCanceled () | TaskStatus.Faulted, _ -> tcs.SetException (Unchecked.nonNull f.Exception).InnerExceptions @@ -271,7 +296,7 @@ module Task = | Faulted exn , _ -> FromExceptions exn | Canceled , _ -> canceled else - let tcs = TaskCompletionSource<'T1 * 'T2> () + let tcs = TaskCompletionSource<'T1 * 'T2> tcsOptions match task1.Status, task2.Status with | TaskStatus.Canceled, _ -> tcs.SetCanceled () | TaskStatus.Faulted, _ -> tcs.SetException (Unchecked.nonNull task1.Exception).InnerExceptions @@ -313,11 +338,12 @@ module Task = let ignore (source: Task) = let source = nullArgCheck (nameof source) source - if source.IsCompletedSuccessfully then result () - elif source.IsFaulted then FromExceptions (Unchecked.nonNull source.Exception) - elif source.IsCanceled then canceled - else - let tcs = TaskCompletionSource () + match source.Status with + | TaskStatus.RanToCompletion -> result () + | TaskStatus.Faulted -> FromExceptions (Unchecked.nonNull source.Exception) + | TaskStatus.Canceled -> canceled + | _ -> + let tcs = TaskCompletionSource tcsOptions let k (t: Task) : unit = if t.IsCanceled then tcs.SetCanceled () elif t.IsFaulted then tcs.SetException (Unchecked.nonNull source.Exception).InnerExceptions @@ -329,7 +355,7 @@ module Task = let rec tryWith (body: unit -> Task<'T>) (compensation: exn -> Task<'T>) : Task<'T> = let runCompensation exn = try compensation exn - with e -> Task.FromException<'T> e + with e -> raise e let unwrapException (agg: AggregateException) = if agg.InnerExceptions.Count = 1 then agg.InnerExceptions.[0] else agg :> Exception @@ -353,12 +379,12 @@ module Task = try compensation () reraise () - with e -> Task.FromException<'T> e + with e -> raise e if task.IsCompleted then try compensation () task - with e -> Task.FromException<'T> e + with e -> raise e else task.ContinueWith(fun (x: Task<'T>) -> tryFinally (fun () -> x) compensation).Unwrap () @@ -401,7 +427,7 @@ module Task = let inline recover ([]mapper: exn -> 'T) (source: Task<'T>) : Task<'T> = let source = nullArgCheck (nameof source) source - tryWith (fun () -> source) (mapper >> Task.FromResult) + tryWith (fun () -> source) (mapper >> result) /// Maps the exception of a faulted task to another exception. /// Mapping function from exception to exception. @@ -415,7 +441,7 @@ module Task = | Faulted exn -> FromExceptions (AggregateException (mapper exn)) | _ -> source else - let tcs = TaskCompletionSource<'T> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'T> tcsOptions let k = function | Succeeded r -> tcs.SetResult r | Faulted aex -> tcs.SetException (AggregateException (mapper aex)).InnerExceptions @@ -430,13 +456,8 @@ module Task = /// The resulting Task. let ofResult (source: Result<'T, exn>) : Task<'T> = match source with - | Ok x -> Task.FromResult x - | Error exn -> Task.FromException<'T> exn - - /// Creates a Task that's completed unsuccessfully with the specified exception. - /// The exception to be raised. - /// A Task that is completed unsuccessfully with the specified exception. - let raise<'T> (exn: exn) : Task<'T> = Task.FromException<'T> exn + | Ok x -> result x + | Error exn -> raise exn /// Workaround to fix signatures without breaking binary compatibility. diff --git a/src/FSharpPlus/Extensions/ValueTask.fs b/src/FSharpPlus/Extensions/ValueTask.fs index 289223ea0..b23117d1c 100644 --- a/src/FSharpPlus/Extensions/ValueTask.fs +++ b/src/FSharpPlus/Extensions/ValueTask.fs @@ -16,7 +16,7 @@ module ValueTask = if t.IsCompletedSuccessfully then Succeeded t.Result elif t.IsFaulted then Faulted (Unchecked.nonNull (t.AsTask().Exception)) elif t.IsCanceled then Canceled - else invalidOp "Internal error: The task is not yet completed." + else invalidOp "Internal error: The task is not yet in a final state." let inline internal continueTask (tcs: TaskCompletionSource<'Result>) (k: 't -> unit) (x: ValueTask<'t>) = let f = function @@ -25,6 +25,11 @@ module ValueTask = | Canceled -> tcs.SetCanceled () x.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted (fun () -> f x) + #if NET5_0_OR_GREATER + let [] private tcsOptions = TaskCreationOptions.RunContinuationsAsynchronously + #else + let private tcsOptions = () + #endif /// Creates a ValueTask that's completed successfully with the specified value. /// @@ -33,30 +38,49 @@ module ValueTask = #if NET5_0_OR_GREATER ValueTask.FromResult value #else - let tcs = TaskCompletionSource<'T> () + let tcs = TaskCompletionSource<'T> tcsOptions tcs.SetResult value ValueTask<'T> tcs.Task #endif + + /// Creates a ValueTask that's completed unsuccessfully with the specified exception. + /// The exception to be raised. + /// A ValueTask that is completed unsuccessfully with the specified exception. + let raise<'T> (exn: exn) : ValueTask<'T> = + #if NET5_0_OR_GREATER + ValueTask.FromException<'T> exn + #else + let tcs = TaskCompletionSource<'T> tcsOptions + tcs.SetException exn + ValueTask<'T> tcs.Task + #endif /// Creates a Task that's completed unsuccessfully with the specified exceptions. - /// The AggregateException to be raised. + /// The AggregateException to be raised. /// A Task that is completed unsuccessfully with the specified exceptions. /// /// Prefer this function to handle AggregateExceptions over Task.FromException as it handles them correctly. /// let inline internal FromExceptions<'T> (aex: AggregateException) : ValueTask<'T> = + #if NET5_0_OR_GREATER match aex with | agg when agg.InnerExceptions.Count = 1 -> ValueTask.FromException<'T> agg.InnerExceptions[0] - | agg -> - let tcs = TaskCompletionSource<'T> () - tcs.SetException agg.InnerExceptions + | _ -> + #endif + let tcs = TaskCompletionSource<'T> tcsOptions + tcs.SetException aex.InnerExceptions ValueTask<'T> tcs.Task - let private cancellationTokenSingleton = CancellationToken true - /// Creates a ValueTask that's canceled. /// A ValueTask that's canceled. - let canceled<'T> : ValueTask<'T> = ValueTask.FromCanceled<'T> cancellationTokenSingleton + let canceled<'T> : ValueTask<'T> = + #if NET5_0_OR_GREATER + ValueTask.FromCanceled<'T> (CancellationToken true) + #else + let tcs = TaskCompletionSource<'T> tcsOptions + tcs.SetCanceled () + ValueTask<'T> tcs.Task + #endif /// Creates a ValueTask workflow from 'source' workflow, mapping its result with 'mapper'. /// The mapping function. @@ -65,11 +89,11 @@ module ValueTask = let map (mapper: 'T -> 'U) (source: ValueTask<'T>) : ValueTask<'U> = if source.IsCompleted then match source with - | Succeeded r -> try result (mapper r) with e -> ValueTask.FromException<_> e + | Succeeded r -> try result (mapper r) with e -> raise e | Faulted exn -> FromExceptions exn | Canceled -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions source |> continueTask tcs (fun r -> try tcs.SetResult (mapper r) with e -> tcs.SetException e) tcs.Task |> ValueTask<'U> @@ -82,13 +106,13 @@ module ValueTask = let lift2 (mapper: 'T1 -> 'T2 -> 'U) (task1: ValueTask<'T1>) (task2: ValueTask<'T2>) : ValueTask<'U> = if task1.IsCompleted && task2.IsCompleted then match task1, task2 with - | Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> ValueTask.FromException<_> e + | Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> raise e | Succeeded _ , Faulted exn -> FromExceptions exn | Succeeded _ , Canceled -> canceled | Faulted exn , _ -> FromExceptions exn | Canceled , _ -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions if task1.IsCanceled then tcs.SetCanceled () elif task1.IsFaulted then tcs.SetException (Unchecked.nonNull (task1.AsTask().Exception)).InnerExceptions elif task2.IsCanceled then tcs.SetCanceled () @@ -107,7 +131,7 @@ module ValueTask = let lift3 (mapper: 'T1 -> 'T2 -> 'T3 -> 'U) (task1: ValueTask<'T1>) (task2: ValueTask<'T2>) (task3: ValueTask<'T3>) : ValueTask<'U> = if task1.IsCompleted && task2.IsCompleted && task3.IsCompleted then match task1, task2, task3 with - | Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> ValueTask.FromException<_> e + | Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> raise e | Faulted exn , _ , _ -> FromExceptions exn | Canceled , _ , _ -> canceled | _ , Faulted exn , _ -> FromExceptions exn @@ -115,7 +139,7 @@ module ValueTask = | _ , _ , Faulted exn -> FromExceptions exn | _ , _ , Canceled -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions if task1.IsCanceled then tcs.SetCanceled () elif task1.IsFaulted then tcs.SetException (Unchecked.nonNull (task1.AsTask().Exception)).InnerExceptions elif task2.IsCanceled then tcs.SetCanceled () @@ -139,9 +163,9 @@ module ValueTask = let map2 mapper (task1: ValueTask<'T1>) (task2: ValueTask<'T2>) : ValueTask<'U> = if task1.IsCompletedSuccessfully && task2.IsCompletedSuccessfully then try result (mapper task1.Result task2.Result) - with e -> ValueTask.FromException<'U> e + with e -> raise e else - let tcs = TaskCompletionSource<_> () + let tcs = TaskCompletionSource<_> tcsOptions let r1 = ref Unchecked.defaultof<_> let r2 = ref Unchecked.defaultof<_> let mutable cancelled = false @@ -183,9 +207,9 @@ module ValueTask = let map3 mapper (task1: ValueTask<'T1>) (task2: ValueTask<'T2>) (task3: ValueTask<'T3>) : ValueTask<'U> = if task1.IsCompletedSuccessfully && task2.IsCompletedSuccessfully && task3.IsCompletedSuccessfully then try result (mapper task1.Result task2.Result task3.Result) - with e -> ValueTask.FromException<'U> e + with e -> raise e else - let tcs = TaskCompletionSource<_> () + let tcs = TaskCompletionSource<_> tcsOptions let r1 = ref Unchecked.defaultof<_> let r2 = ref Unchecked.defaultof<_> let r3 = ref Unchecked.defaultof<_> @@ -226,13 +250,13 @@ module ValueTask = let apply (f: ValueTask<'T -> 'U>) (x: ValueTask<'T>) : ValueTask<'U> = if f.IsCompleted && x.IsCompleted then match f, x with - | Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> ValueTask.FromException<_> e + | Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> raise e | Succeeded _ , Faulted exn -> FromExceptions exn | Succeeded _ , Canceled -> canceled | Faulted exn , _ -> FromExceptions exn | Canceled , _ -> canceled else - let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'U> tcsOptions if f.IsCanceled then tcs.SetCanceled () elif f.IsFaulted then tcs.SetException (Unchecked.nonNull (f.AsTask().Exception)).InnerExceptions elif x.IsCanceled then tcs.SetCanceled () @@ -252,7 +276,7 @@ module ValueTask = | Faulted exn , _ -> FromExceptions exn | Canceled , _ -> canceled else - let tcs = TaskCompletionSource<'T1 * 'T2> () + let tcs = TaskCompletionSource<'T1 * 'T2> tcsOptions if task1.IsCanceled then tcs.SetCanceled () elif task1.IsFaulted then tcs.SetException (Unchecked.nonNull (task1.AsTask().Exception)).InnerExceptions elif task2.IsCanceled then tcs.SetCanceled () @@ -291,7 +315,7 @@ module ValueTask = elif source.IsFaulted then FromExceptions (Unchecked.nonNull (source.AsTask().Exception)) elif source.IsCanceled then canceled else - let tcs = TaskCompletionSource () + let tcs = TaskCompletionSource tcsOptions let k (t: ValueTask) : unit = if t.IsCanceled then tcs.SetCanceled () elif t.IsFaulted then tcs.SetException (Unchecked.nonNull (source.AsTask().Exception)).InnerExceptions @@ -303,7 +327,7 @@ module ValueTask = let inline tryWith ([]compensation: exn -> ValueTask<'T>) ([]body: unit -> ValueTask<'T>) : ValueTask<'T> = let runCompensation exn = try compensation exn - with e -> ValueTask.FromException<'T> e + with e -> raise e let unwrapException (agg: AggregateException) = if agg.InnerExceptions.Count = 1 then agg.InnerExceptions.[0] else agg :> Exception @@ -328,12 +352,12 @@ module ValueTask = try compensation () reraise () - with e -> ValueTask.FromException<'T> e + with e -> raise e if task.IsCompleted then try compensation () task - with e -> ValueTask.FromException<'T> e + with e -> raise e else task.AsTask().ContinueWith(fun (x: Task<'T>) -> Task.tryFinally compensation (fun () -> x)).Unwrap () |> ValueTask<'T> @@ -367,7 +391,7 @@ module ValueTask = /// A successful resulting task. /// The result is always a successful task, unless the mapping function itself throws an exception. let inline recover ([]mapper: exn -> 'T) (source: ValueTask<'T>) : ValueTask<'T> = - tryWith (mapper >> ValueTask.FromResult) (fun () -> source) + tryWith (mapper >> result) (fun () -> source) /// Maps the exception of a faulted task to another exception. /// Mapping function from exception to exception. @@ -379,7 +403,7 @@ module ValueTask = | Faulted exn -> FromExceptions (AggregateException (mapper exn)) | _ -> source else - let tcs = TaskCompletionSource<'T> TaskCreationOptions.RunContinuationsAsynchronously + let tcs = TaskCompletionSource<'T> tcsOptions let k = function | Succeeded r -> tcs.SetResult r | Faulted aex -> tcs.SetException (AggregateException (mapper aex)).InnerExceptions @@ -394,12 +418,7 @@ module ValueTask = /// The resulting Task. let ofResult (source: Result<'T, exn>) : ValueTask<'T> = match source with - | Ok x -> ValueTask.FromResult x - | Error exn -> ValueTask.FromException<'T> exn - - /// Creates a ValueTask that's completed unsuccessfully with the specified exception. - /// The exception to be raised. - /// A ValueTask that is completed unsuccessfully with the specified exception. - let raise<'T> (exn: exn) : ValueTask<'T> = ValueTask.FromException<'T> exn + | Ok x -> result x + | Error exn -> raise exn #endif