Skip to content

Commit 6fd3ff3

Browse files
committed
Reorganize code for multitargeting
1 parent 57e7d2e commit 6fd3ff3

2 files changed

Lines changed: 187 additions & 69 deletions

File tree

src/FSharpPlus/Extensions/Task.fs

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ module Task =
1515

1616
/// Active pattern to match the state of a completed Task
1717
let inline internal (|Succeeded|Canceled|Faulted|) (t: Task<'a>) =
18-
if t.IsFaulted then Faulted (Unchecked.nonNull (t.Exception))
19-
elif t.IsCanceled then Canceled
20-
elif t.IsCompleted then Succeeded t.Result
21-
else invalidOp "Internal error: The task is not yet completed."
18+
match t.Status with
19+
| TaskStatus.RanToCompletion -> Succeeded t.Result
20+
| TaskStatus.Faulted -> Faulted (Unchecked.nonNull t.Exception)
21+
| TaskStatus.Canceled -> Canceled
22+
| _ -> invalidOp $"Internal error: The task is not yet in a final state. State = TaskStatus.%A{t.Status}"
2223

2324
let inline internal continueTask (tcs: TaskCompletionSource<'Result>) (k: 't -> unit) (x: Task<'t>) =
2425
let f = function
@@ -27,32 +28,57 @@ module Task =
2728
| Canceled -> tcs.SetCanceled ()
2829
x.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted (fun () -> f x)
2930

31+
let [<Literal>] private tcsOptions =
32+
#if NET5_0_OR_GREATER
33+
TaskCreationOptions.RunContinuationsAsynchronously
34+
#else
35+
()
36+
#endif
3037

3138
/// <summary>Creates a Task that's completed successfully with the specified value.</summary>
3239
/// <param name="value"></param>
3340
/// <returns>A Task that is completed successfully with the specified value.</returns>
3441
let result (value: 'T) : Task<'T> = Task.FromResult value
35-
42+
43+
44+
/// <summary>Creates a Task that's completed unsuccessfully with the specified exception.</summary>
45+
/// <param name="exn">The exception to be raised.</param>
46+
/// <returns>A Task that is completed unsuccessfully with the specified exception.</returns>
47+
let raise<'T> (exn: exn) : Task<'T> =
48+
#if NET5_0_OR_GREATER
49+
Task.FromException<'T> exn
50+
#else
51+
let tcs = TaskCompletionSource<'T> tcsOptions
52+
tcs.SetException exn
53+
tcs.Task
54+
#endif
55+
3656
/// <summary>Creates a Task that's completed unsuccessfully with the specified exceptions.</summary>
37-
/// <param name="exn">The AggregateException to be raised.</param>
57+
/// <param name="aex">The AggregateException to be raised.</param>
3858
/// <returns>A Task that is completed unsuccessfully with the specified exceptions.</returns>
3959
/// <remarks>
4060
/// Prefer this function to handle AggregateExceptions over Task.FromException as it handles them correctly.
4161
/// </remarks>
42-
let internal FromExceptions<'T> (aex: AggregateException) : Task<'T> =
62+
let inline internal FromExceptions<'T> (aex: AggregateException) : Task<'T> =
63+
#if NET5_0_OR_GREATER
4364
match aex with
4465
| agg when agg.InnerExceptions.Count = 1 -> Task.FromException<'T> agg.InnerExceptions[0]
45-
| agg ->
46-
let tcs = TaskCompletionSource<'T> ()
47-
tcs.SetException agg.InnerExceptions
66+
| _ ->
67+
#endif
68+
let tcs = TaskCompletionSource<'T> tcsOptions
69+
tcs.SetException aex.InnerExceptions
4870
tcs.Task
4971

50-
let private cancellationTokenSingleton = CancellationToken true
51-
5272
/// <summary>Creates a Task that's canceled.</summary>
5373
/// <returns>A Task that's canceled.</returns>
54-
let canceled<'T> : Task<'T> = Task.FromCanceled<'T> cancellationTokenSingleton
55-
74+
let canceled<'T> : Task<'T> =
75+
#if NET5_0_OR_GREATER
76+
Task.FromCanceled<'T> (CancellationToken true)
77+
#else
78+
let tcs = TaskCompletionSource<'T> tcsOptions
79+
tcs.SetCanceled ()
80+
tcs.Task
81+
#endif
5682

5783
/// <summary>Creates a task workflow from 'source' workflow, mapping its result with 'mapper'.</summary>
5884
/// <param name="mapper">The mapping function.</param>
@@ -67,11 +93,11 @@ module Task =
6793

6894
if source.IsCompleted then
6995
match source with
70-
| Succeeded r -> try result (mapper r) with e -> Task.FromException<_> e
96+
| Succeeded r -> try result (mapper r) with e -> raise e
7197
| Faulted exn -> FromExceptions exn
7298
| Canceled -> canceled
7399
else
74-
let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously
100+
let tcs = TaskCompletionSource<'U> tcsOptions
75101
source |> continueTask tcs (fun r -> try tcs.SetResult (mapper r) with e -> tcs.SetException e)
76102
tcs.Task
77103

@@ -91,13 +117,13 @@ module Task =
91117

92118
if task1.IsCompleted && task2.IsCompleted then
93119
match task1, task2 with
94-
| Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> Task.FromException<_> e
120+
| Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> raise e
95121
| Succeeded _ , Faulted exn -> FromExceptions exn
96122
| Succeeded _ , Canceled -> canceled
97123
| Faulted exn , _ -> FromExceptions exn
98124
| Canceled , _ -> canceled
99125
else
100-
let tcs = TaskCompletionSource<'U> ()
126+
let tcs = TaskCompletionSource<'U> tcsOptions
101127

102128
match task1.Status, task2.Status with
103129
| TaskStatus.Canceled, _ -> tcs.SetCanceled ()
@@ -128,15 +154,15 @@ module Task =
128154

129155
if task1.IsCompleted && task2.IsCompleted && task3.IsCompleted then
130156
match task1, task2, task3 with
131-
| Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> Task.FromException<_> e
157+
| Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> raise e
132158
| Faulted exn , _ , _ -> FromExceptions exn
133159
| Canceled , _ , _ -> canceled
134160
| _ , Faulted exn , _ -> FromExceptions exn
135161
| _ , Canceled , _ -> canceled
136162
| _ , _ , Faulted exn -> FromExceptions exn
137163
| _ , _ , Canceled -> canceled
138164
else
139-
let tcs = TaskCompletionSource<'U> ()
165+
let tcs = TaskCompletionSource<'U> tcsOptions
140166
match task1.Status, task2.Status, task3.Status with
141167
| TaskStatus.Canceled, _ , _ -> tcs.SetCanceled ()
142168
| TaskStatus.Faulted , _ , _ -> tcs.SetException (Unchecked.nonNull task1.Exception).InnerExceptions
@@ -168,9 +194,9 @@ module Task =
168194
#endif
169195

170196
if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion then
171-
try result (mapper task1.Result task2.Result) with e -> Task.FromException<'U> e
197+
try result (mapper task1.Result task2.Result) with e -> raise e
172198
else
173-
let tcs = TaskCompletionSource<_> ()
199+
let tcs = TaskCompletionSource<_> tcsOptions
174200
let r1 = ref Unchecked.defaultof<_>
175201
let r2 = ref Unchecked.defaultof<_>
176202
let mutable cancelled = false
@@ -222,9 +248,9 @@ module Task =
222248

223249
if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion && task3.Status = TaskStatus.RanToCompletion then
224250
try result (mapper task1.Result task2.Result task3.Result)
225-
with e -> Task.FromException<'U> e
251+
with e -> raise e
226252
else
227-
let tcs = TaskCompletionSource<_> ()
253+
let tcs = TaskCompletionSource<_> tcsOptions
228254
let r1 = ref Unchecked.defaultof<_>
229255
let r2 = ref Unchecked.defaultof<_>
230256
let r3 = ref Unchecked.defaultof<_>
@@ -273,13 +299,13 @@ module Task =
273299

274300
if f.IsCompleted && x.IsCompleted then
275301
match f, x with
276-
| Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> Task.FromException<_> e
302+
| Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> raise e
277303
| Succeeded _ , Faulted exn -> FromExceptions exn
278304
| Succeeded _ , Canceled -> canceled
279305
| Faulted exn , _ -> FromExceptions exn
280306
| Canceled , _ -> canceled
281307
else
282-
let tcs = TaskCompletionSource<'U> ()
308+
let tcs = TaskCompletionSource<'U> tcsOptions
283309
match f.Status, x.Status with
284310
| TaskStatus.Canceled, _ -> tcs.SetCanceled ()
285311
| TaskStatus.Faulted, _ -> tcs.SetException (Unchecked.nonNull f.Exception).InnerExceptions
@@ -307,7 +333,7 @@ module Task =
307333
| Faulted exn , _ -> FromExceptions exn
308334
| Canceled , _ -> canceled
309335
else
310-
let tcs = TaskCompletionSource<'T1 * 'T2> ()
336+
let tcs = TaskCompletionSource<'T1 * 'T2> tcsOptions
311337
match task1.Status, task2.Status with
312338
| TaskStatus.Canceled, _ -> tcs.SetCanceled ()
313339
| TaskStatus.Faulted, _ -> tcs.SetException (Unchecked.nonNull task1.Exception).InnerExceptions
@@ -357,11 +383,12 @@ module Task =
357383
raiseIfNull "source" source
358384
#endif
359385

360-
if source.IsCompletedSuccessfully then result ()
361-
elif source.IsFaulted then FromExceptions (Unchecked.nonNull source.Exception)
362-
elif source.IsCanceled then canceled
363-
else
364-
let tcs = TaskCompletionSource<unit> ()
386+
match source.Status with
387+
| TaskStatus.RanToCompletion -> result ()
388+
| TaskStatus.Faulted -> FromExceptions (Unchecked.nonNull source.Exception)
389+
| TaskStatus.Canceled -> canceled
390+
| _ ->
391+
let tcs = TaskCompletionSource<unit> tcsOptions
365392
let k (t: Task) : unit =
366393
if t.IsCanceled then tcs.SetCanceled ()
367394
elif t.IsFaulted then tcs.SetException (Unchecked.nonNull source.Exception).InnerExceptions
@@ -373,7 +400,7 @@ module Task =
373400
let rec tryWith (body: unit -> Task<'T>) (compensation: exn -> Task<'T>) : Task<'T> =
374401
let runCompensation exn =
375402
try compensation exn
376-
with e -> Task.FromException<'T> e
403+
with e -> raise e
377404
let unwrapException (agg: AggregateException) =
378405
if agg.InnerExceptions.Count = 1 then agg.InnerExceptions.[0]
379406
else agg :> Exception
@@ -397,12 +424,12 @@ module Task =
397424
try
398425
compensation ()
399426
reraise ()
400-
with e -> Task.FromException<'T> e
427+
with e -> raise e
401428
if task.IsCompleted then
402429
try
403430
compensation ()
404431
task
405-
with e -> Task.FromException<'T> e
432+
with e -> raise e
406433
else
407434
task.ContinueWith(fun (x: Task<'T>) -> tryFinally (fun () -> x) compensation).Unwrap ()
408435

@@ -447,11 +474,46 @@ module Task =
447474
raiseIfNull "source" source
448475
#endif
449476
orElseWith (fun _ -> fallbackTask) source
477+
478+
/// <summary>Attempts to recover from a potentially failed task by mapping the exception to a successful result.</summary>
479+
/// <param name="mapper">Mapping function from exception to result.</param>
480+
/// <param name="source">The source task.</param>
481+
/// <returns>A successful resulting task.</returns>
482+
/// <remarks>The result is always a successful task, unless the mapping function itself throws an exception.</remarks>
483+
let inline recover ([<InlineIfLambda>]mapper: exn -> 'T) (source: Task<'T>) : Task<'T> =
484+
let source = nullArgCheck (nameof source) source
450485

451-
/// <summary>Creates a Task that's completed unsuccessfully with the specified exception.</summary>
452-
/// <param name="exn">The exception to be raised.</param>
453-
/// <returns>A Task that is completed unsuccessfully with the specified exception.</returns>
454-
let raise<'T> (exn: exn) : Task<'T> = Task.FromException<'T> exn
486+
tryWith (fun () -> source) (mapper >> result)
487+
488+
/// <summary>Maps the exception of a faulted task to another exception.</summary>
489+
/// <param name="mapper">Mapping function from exception to exception.</param>
490+
/// <param name="source">The source task.</param>
491+
/// <returns>The resulting task.</returns>
492+
let inline mapError ([<InlineIfLambda>]mapper: exn -> exn) (source: Task<'T>) : Task<'T> =
493+
let source = nullArgCheck (nameof source) source
494+
495+
if source.IsCompleted then
496+
match source with
497+
| Faulted exn -> FromExceptions (AggregateException (mapper exn))
498+
| _ -> source
499+
else
500+
let tcs = TaskCompletionSource<'T> tcsOptions
501+
let k = function
502+
| Succeeded r -> tcs.SetResult r
503+
| Faulted aex -> tcs.SetException (AggregateException (mapper aex)).InnerExceptions
504+
| Canceled -> tcs.SetCanceled ()
505+
source.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted (fun () -> k source)
506+
tcs.Task
507+
508+
/// Creates a Task from a Result value.
509+
/// If the Result is Ok, the Task will complete successfully with the value.
510+
/// If the Result is Error, the Task will complete unsuccessfully with the exception.
511+
/// <param name="source">The source Result.</param>
512+
/// <returns>The resulting Task.</returns>
513+
let ofResult (source: Result<'T, exn>) : Task<'T> =
514+
match source with
515+
| Ok x -> result x
516+
| Error exn -> raise exn
455517

456518

457519
/// Workaround to fix signatures without breaking binary compatibility.

0 commit comments

Comments
 (0)