Skip to content

Commit e20c017

Browse files
gustywallymathieu
authored andcommitted
Reorganize code for multitargeting
1 parent 553e8d4 commit e20c017

2 files changed

Lines changed: 186 additions & 70 deletions

File tree

src/FSharpPlus/Extensions/Task.fs

Lines changed: 100 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 (sprintf "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,56 @@ module Task =
2728
| Canceled -> tcs.SetCanceled ()
2829
x.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted (fun () -> f x)
2930

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

3137
/// <summary>Creates a Task that's completed successfully with the specified value.</summary>
3238
/// <param name="value"></param>
3339
/// <returns>A Task that is completed successfully with the specified value.</returns>
3440
let result (value: 'T) : Task<'T> = Task.FromResult value
35-
41+
42+
43+
/// <summary>Creates a Task that's completed unsuccessfully with the specified exception.</summary>
44+
/// <param name="exn">The exception to be raised.</param>
45+
/// <returns>A Task that is completed unsuccessfully with the specified exception.</returns>
46+
let raise<'T> (exn: exn) : Task<'T> =
47+
#if NET5_0_OR_GREATER
48+
Task.FromException<'T> exn
49+
#else
50+
let tcs = TaskCompletionSource<'T> tcsOptions
51+
tcs.SetException exn
52+
tcs.Task
53+
#endif
54+
3655
/// <summary>Creates a Task that's completed unsuccessfully with the specified exceptions.</summary>
37-
/// <param name="exn">The AggregateException to be raised.</param>
56+
/// <param name="aex">The AggregateException to be raised.</param>
3857
/// <returns>A Task that is completed unsuccessfully with the specified exceptions.</returns>
3958
/// <remarks>
4059
/// Prefer this function to handle AggregateExceptions over Task.FromException as it handles them correctly.
4160
/// </remarks>
42-
let internal FromExceptions<'T> (aex: AggregateException) : Task<'T> =
61+
let inline internal FromExceptions<'T> (aex: AggregateException) : Task<'T> =
62+
#if NET5_0_OR_GREATER
4363
match aex with
4464
| agg when agg.InnerExceptions.Count = 1 -> Task.FromException<'T> agg.InnerExceptions[0]
45-
| agg ->
46-
let tcs = TaskCompletionSource<'T> ()
47-
tcs.SetException agg.InnerExceptions
65+
| _ ->
66+
#endif
67+
let tcs = TaskCompletionSource<'T> tcsOptions
68+
tcs.SetException aex.InnerExceptions
4869
tcs.Task
4970

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

5782
/// <summary>Creates a task workflow from 'source' workflow, mapping its result with 'mapper'.</summary>
5883
/// <param name="mapper">The mapping function.</param>
@@ -67,11 +92,11 @@ module Task =
6792

6893
if source.IsCompleted then
6994
match source with
70-
| Succeeded r -> try result (mapper r) with e -> Task.FromException<_> e
95+
| Succeeded r -> try result (mapper r) with e -> raise e
7196
| Faulted exn -> FromExceptions exn
7297
| Canceled -> canceled
7398
else
74-
let tcs = TaskCompletionSource<'U> TaskCreationOptions.RunContinuationsAsynchronously
99+
let tcs = TaskCompletionSource<'U> tcsOptions
75100
source |> continueTask tcs (fun r -> try tcs.SetResult (mapper r) with e -> tcs.SetException e)
76101
tcs.Task
77102

@@ -91,13 +116,13 @@ module Task =
91116

92117
if task1.IsCompleted && task2.IsCompleted then
93118
match task1, task2 with
94-
| Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> Task.FromException<_> e
119+
| Succeeded r1, Succeeded r2 -> try result (mapper r1 r2) with e -> raise e
95120
| Succeeded _ , Faulted exn -> FromExceptions exn
96121
| Succeeded _ , Canceled -> canceled
97122
| Faulted exn , _ -> FromExceptions exn
98123
| Canceled , _ -> canceled
99124
else
100-
let tcs = TaskCompletionSource<'U> ()
125+
let tcs = TaskCompletionSource<'U> tcsOptions
101126

102127
match task1.Status, task2.Status with
103128
| TaskStatus.Canceled, _ -> tcs.SetCanceled ()
@@ -128,15 +153,15 @@ module Task =
128153

129154
if task1.IsCompleted && task2.IsCompleted && task3.IsCompleted then
130155
match task1, task2, task3 with
131-
| Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> Task.FromException<_> e
156+
| Succeeded r1, Succeeded r2, Succeeded r3 -> try result (mapper r1 r2 r3) with e -> raise e
132157
| Faulted exn , _ , _ -> FromExceptions exn
133158
| Canceled , _ , _ -> canceled
134159
| _ , Faulted exn , _ -> FromExceptions exn
135160
| _ , Canceled , _ -> canceled
136161
| _ , _ , Faulted exn -> FromExceptions exn
137162
| _ , _ , Canceled -> canceled
138163
else
139-
let tcs = TaskCompletionSource<'U> ()
164+
let tcs = TaskCompletionSource<'U> tcsOptions
140165
match task1.Status, task2.Status, task3.Status with
141166
| TaskStatus.Canceled, _ , _ -> tcs.SetCanceled ()
142167
| TaskStatus.Faulted , _ , _ -> tcs.SetException (Unchecked.nonNull task1.Exception).InnerExceptions
@@ -168,9 +193,9 @@ module Task =
168193
#endif
169194

170195
if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion then
171-
try result (mapper task1.Result task2.Result) with e -> Task.FromException<'U> e
196+
try result (mapper task1.Result task2.Result) with e -> raise e
172197
else
173-
let tcs = TaskCompletionSource<_> ()
198+
let tcs = TaskCompletionSource<_> tcsOptions
174199
let r1 = ref Unchecked.defaultof<_>
175200
let r2 = ref Unchecked.defaultof<_>
176201
let mutable cancelled = false
@@ -222,9 +247,9 @@ module Task =
222247

223248
if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion && task3.Status = TaskStatus.RanToCompletion then
224249
try result (mapper task1.Result task2.Result task3.Result)
225-
with e -> Task.FromException<'U> e
250+
with e -> raise e
226251
else
227-
let tcs = TaskCompletionSource<_> ()
252+
let tcs = TaskCompletionSource<_> tcsOptions
228253
let r1 = ref Unchecked.defaultof<_>
229254
let r2 = ref Unchecked.defaultof<_>
230255
let r3 = ref Unchecked.defaultof<_>
@@ -273,13 +298,13 @@ module Task =
273298

274299
if f.IsCompleted && x.IsCompleted then
275300
match f, x with
276-
| Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> Task.FromException<_> e
301+
| Succeeded r1, Succeeded r2 -> try result (r1 r2) with e -> raise e
277302
| Succeeded _ , Faulted exn -> FromExceptions exn
278303
| Succeeded _ , Canceled -> canceled
279304
| Faulted exn , _ -> FromExceptions exn
280305
| Canceled , _ -> canceled
281306
else
282-
let tcs = TaskCompletionSource<'U> ()
307+
let tcs = TaskCompletionSource<'U> tcsOptions
283308
match f.Status, x.Status with
284309
| TaskStatus.Canceled, _ -> tcs.SetCanceled ()
285310
| TaskStatus.Faulted, _ -> tcs.SetException (Unchecked.nonNull f.Exception).InnerExceptions
@@ -307,7 +332,7 @@ module Task =
307332
| Faulted exn , _ -> FromExceptions exn
308333
| Canceled , _ -> canceled
309334
else
310-
let tcs = TaskCompletionSource<'T1 * 'T2> ()
335+
let tcs = TaskCompletionSource<'T1 * 'T2> tcsOptions
311336
match task1.Status, task2.Status with
312337
| TaskStatus.Canceled, _ -> tcs.SetCanceled ()
313338
| TaskStatus.Faulted, _ -> tcs.SetException (Unchecked.nonNull task1.Exception).InnerExceptions
@@ -357,11 +382,12 @@ module Task =
357382
raiseIfNull "source" source
358383
#endif
359384

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> ()
385+
match source.Status with
386+
| TaskStatus.RanToCompletion -> result ()
387+
| TaskStatus.Faulted -> FromExceptions (Unchecked.nonNull source.Exception)
388+
| TaskStatus.Canceled -> canceled
389+
| _ ->
390+
let tcs = TaskCompletionSource<unit> tcsOptions
365391
let k (t: Task) : unit =
366392
if t.IsCanceled then tcs.SetCanceled ()
367393
elif t.IsFaulted then tcs.SetException (Unchecked.nonNull source.Exception).InnerExceptions
@@ -373,7 +399,7 @@ module Task =
373399
let rec tryWith (body: unit -> Task<'T>) (compensation: exn -> Task<'T>) : Task<'T> =
374400
let runCompensation exn =
375401
try compensation exn
376-
with e -> Task.FromException<'T> e
402+
with e -> raise e
377403
let unwrapException (agg: AggregateException) =
378404
if agg.InnerExceptions.Count = 1 then agg.InnerExceptions.[0]
379405
else agg :> Exception
@@ -397,12 +423,12 @@ module Task =
397423
try
398424
compensation ()
399425
reraise ()
400-
with e -> Task.FromException<'T> e
426+
with e -> raise e
401427
if task.IsCompleted then
402428
try
403429
compensation ()
404430
task
405-
with e -> Task.FromException<'T> e
431+
with e -> raise e
406432
else
407433
task.ContinueWith(fun (x: Task<'T>) -> tryFinally (fun () -> x) compensation).Unwrap ()
408434

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

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

456517

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

0 commit comments

Comments
 (0)