@@ -6,22 +6,36 @@ namespace FSharpPlus
66[<RequireQualifiedAccess>]
77module ValueTask =
88
9+ open System
10+ open System.Threading
911 open System.Threading .Tasks
1012
1113 let inline internal (| Succeeded | Canceled | Faulted |) ( t : ValueTask < 'T >) =
1214 if t.IsCompletedSuccessfully then Succeeded t.Result
1315 elif t.IsCanceled then Canceled
14- else Faulted ( t.AsTask() .Exception.InnerExceptions )
16+ else Faulted ( t.AsTask() .Exception)
1517
1618 let inline internal continueTask ( tcs : TaskCompletionSource < 'Result >) ( x : ValueTask < 't >) ( k : 't -> unit ) =
1719 let f = function
1820 | Succeeded r -> k r
1921 | Canceled -> tcs.SetCanceled ()
20- | Faulted e -> tcs.SetException e
22+ | Faulted e -> tcs.SetException e.InnerExceptions
2123 if x.IsCompleted then f x
22- else
23- let aw = x.GetAwaiter ()
24- aw.OnCompleted ( fun () -> f x)
24+ else x.ConfigureAwait( false ) .GetAwaiter() .UnsafeOnCompleted ( fun () -> f x)
25+
26+ let inline internal continueWith ( x : ValueTask < 't >) f =
27+ if x.IsCompleted then f x
28+ else x.ConfigureAwait( false ) .GetAwaiter() .UnsafeOnCompleted ( fun () -> f x)
29+
30+ /// Creates a ValueTask from a value
31+ let result ( value : 'T ) : ValueTask < 'T > =
32+ #if NET5_ 0_ OR_ GREATER
33+ ValueTask.FromResult value
34+ #else
35+ let tcs = TaskCompletionSource< 'T> ()
36+ tcs.SetResult value
37+ tcs.Task |> ValueTask< 'T>
38+ #endif
2539
2640 /// <summary>Creates a ValueTask workflow from 'source' another, mapping its result with 'f'.</summary>
2741 /// <param name="f">The mapping function.</param>
@@ -62,34 +76,101 @@ module ValueTask =
6276 with e -> tcs.SetException e)))
6377 tcs.Task |> ValueTask< 'W>
6478
65- /// <summary>Creates a task workflow from two workflows 'x' and 'y', mapping its results with 'f'.</summary>
66- /// <remarks>Similar to lift2 but although workflows are started in sequence they might end independently in different order.</remarks>
67- /// <param name="f">The mapping function.</param>
68- /// <param name="x">First ValueTask workflow.</param>
69- /// <param name="y">Second ValueTask workflow.</param>
70- /// <param name="z">Third ValueTask workflow.</param>
71- let map2 ( f : 'T -> 'U -> 'V ) ( x : ValueTask < 'T >) ( y : ValueTask < 'U >) : ValueTask < 'V > =
72- task {
73- let! x ' = x
74- let! y ' = y
75- return f x' y'
76- }
77- |> ValueTask< 'V>
79+ /// <summary>Creates a ValueTask workflow from two workflows, mapping its results with a specified function.</summary>
80+ /// <remarks>Similar to lift2 but although workflows are started in sequence they might end independently in different order
81+ /// and all errors are collected.
82+ /// </remarks>
83+ /// <param name="mapper">The mapping function.</param>
84+ /// <param name="task1">First ValueTask workflow.</param>
85+ /// <param name="task2">Second ValueTask workflow.</param>
86+ let map2 mapper ( task1 : ValueTask < 'T1 >) ( task2 : ValueTask < 'T2 >) : ValueTask < 'U > =
87+ if task1.IsCompletedSuccessfully && task2.IsCompletedSuccessfully then
88+ try result ( mapper task1.Result task2.Result)
89+ with e ->
90+ let tcs = TaskCompletionSource<_> ()
91+ tcs.SetException e
92+ tcs.Task |> ValueTask< 'U>
93+ else
94+ let tcs = TaskCompletionSource<_> ()
95+ let r1 = ref Unchecked.defaultof<_>
96+ let r2 = ref Unchecked.defaultof<_>
97+ let mutable cancelled = false
98+ let failures = [| IReadOnlyCollection.empty; IReadOnlyCollection.empty|]
99+ let pending = ref 2
78100
79- /// <summary>Creates a ValueTask workflow from three workflows 'x', 'y' and z, mapping its results with 'f'.</summary>
80- /// <remarks>Similar to lift3 but although workflows are started in sequence they might end independently in different order.</remarks>
81- /// <param name="f">The mapping function.</param>
82- /// <param name="x">First ValueTask workflow.</param>
83- /// <param name="y">Second ValueTask workflow.</param>
84- /// <param name="z">Third ValueTask workflow.</param>
85- let map3 ( f : 'T -> 'U -> 'V -> 'W ) ( x : ValueTask < 'T >) ( y : ValueTask < 'U >) ( z : ValueTask < 'V >) : ValueTask < 'W > =
86- task {
87- let! x ' = x
88- let! y ' = y
89- let! z ' = z
90- return f x' y' z'
91- }
92- |> ValueTask< 'W>
101+ let trySet () =
102+ if Interlocked.Decrement pending = 0 then
103+ let noFailures = Array.forall IReadOnlyCollection.isEmpty failures
104+ if noFailures && not cancelled then
105+ try tcs.TrySetResult ( mapper r1.Value r2.Value) |> ignore
106+ with e -> tcs.TrySetException e |> ignore
107+ elif noFailures then tcs.TrySetCanceled () |> ignore
108+ else tcs.TrySetException ( failures |> Seq.map AggregateException |> Seq.reduce Exception.add) .InnerExceptions |> ignore
109+
110+ let k ( v : ref < _ >) i t =
111+ match t with
112+ | Succeeded r -> v.Value <- r
113+ | Canceled -> cancelled <- true
114+ | Faulted e -> failures[ i] <- e.InnerExceptions
115+ trySet ()
116+
117+ if task1.IsCompleted && task2.IsCompleted then
118+ task1 |> k r1 0
119+ task2 |> k r2 1
120+ else
121+ continueWith task1 ( k r1 0 )
122+ continueWith task2 ( k r2 1 )
123+ tcs.Task |> ValueTask< 'U>
124+
125+ /// <summary>Creates a ValueTask workflow from three workflows, mapping its results with a specified function.</summary>
126+ /// <remarks>Similar to lift3 but although workflows are started in sequence they might end independently in different order
127+ /// and all errors are collected.
128+ /// </remarks>
129+ /// <param name="mapper">The mapping function.</param>
130+ /// <param name="task1">First ValueTask workflow.</param>
131+ /// <param name="task2">Second ValueTask workflow.</param>
132+ /// <param name="task3">Third ValueTask workflow.</param>
133+ let map3 mapper ( task1 : ValueTask < 'T1 >) ( task2 : ValueTask < 'T2 >) ( task3 : ValueTask < 'T3 >) : ValueTask < 'U > =
134+ if task1.IsCompletedSuccessfully && task2.IsCompletedSuccessfully && task3.IsCompletedSuccessfully then
135+ try result ( mapper task1.Result task2.Result task3.Result)
136+ with e ->
137+ let tcs = TaskCompletionSource<_> ()
138+ tcs.SetException e
139+ tcs.Task |> ValueTask< 'U>
140+ else
141+ let tcs = TaskCompletionSource<_> ()
142+ let r1 = ref Unchecked.defaultof<_>
143+ let r2 = ref Unchecked.defaultof<_>
144+ let r3 = ref Unchecked.defaultof<_>
145+ let mutable cancelled = false
146+ let failures = [| IReadOnlyCollection.empty; IReadOnlyCollection.empty; IReadOnlyCollection.empty|]
147+ let pending = ref 3
148+
149+ let trySet () =
150+ if Interlocked.Decrement pending = 0 then
151+ let noFailures = Array.forall IReadOnlyCollection.isEmpty failures
152+ if noFailures && not cancelled then
153+ try tcs.TrySetResult ( mapper r1.Value r2.Value r3.Value) |> ignore
154+ with e -> tcs.TrySetException e |> ignore
155+ elif noFailures then tcs.TrySetCanceled () |> ignore
156+ else tcs.TrySetException ( failures |> Seq.map AggregateException |> Seq.reduce Exception.add) .InnerExceptions |> ignore
157+
158+ let k ( v : ref < _ >) i t =
159+ match t with
160+ | Succeeded r -> v.Value <- r
161+ | Canceled -> cancelled <- true
162+ | Faulted e -> failures[ i] <- e.InnerExceptions
163+ trySet ()
164+
165+ if task1.IsCompleted && task2.IsCompleted && task3.IsCompleted then
166+ task1 |> k r1 0
167+ task2 |> k r2 1
168+ task3 |> k r3 2
169+ else
170+ continueWith task1 ( k r1 0 )
171+ continueWith task2 ( k r2 1 )
172+ continueWith task3 ( k r3 2 )
173+ tcs.Task |> ValueTask< 'U>
93174
94175 /// <summary>Creates a ValueTask workflow that is the result of applying the resulting function of a ValueTask workflow
95176 /// to the resulting value of another ValueTask workflow</summary>
@@ -111,15 +192,17 @@ module ValueTask =
111192 tcs.SetResult ( x, y)))
112193 tcs.Task |> ValueTask< 'T * 'U>
113194
114- /// <summary>Creates a ValueTask workflow from two workflows 'x' and 'y', tupling its results.</summary>
115- /// <remarks>Similar to zipSequentially but although workflows are started in sequence they might end independently in different order.</remarks>
116- let zip ( x : ValueTask < 'T >) ( y : ValueTask < 'U >) : ValueTask < 'T * 'U > =
117- task {
118- let! x ' = x
119- let! y ' = y
120- return x', y'
121- }
122- |> ValueTask< 'T * 'U>
195+ /// <summary>Creates a ValueTask workflow from two workflows, tupling its results.</summary>
196+ /// <remarks>Similar to zipSequentially but although workflows are started in sequence they might end independently in different order
197+ /// and all errors are collected.
198+ /// </remarks>
199+ let zip ( task1 : ValueTask < 'T1 >) ( task2 : ValueTask < 'T2 >) = map2 ( fun x y -> x, y) task1 task2
200+
201+ /// <summary>Creates a ValueTask workflow from three workflows, tupling its results.</summary>
202+ /// <remarks>Similar to zipSequentially but although workflows are started in sequence they might end independently in different order
203+ /// and all errors are collected.
204+ /// </remarks>
205+ let zip3 ( task1 : ValueTask < 'T1 >) ( task2 : ValueTask < 'T2 >) ( task3 : ValueTask < 'T3 >) = map3 ( fun x y z -> x, y, z) task1 task2 task3
123206
124207 /// Flattens two nested ValueTask into one.
125208 let join ( source : ValueTask < ValueTask < 'T >>) : ValueTask < 'T > =
@@ -152,10 +235,5 @@ module ValueTask =
152235
153236 /// Raises an exception in the ValueTask
154237 let raise ( ``exception`` : exn ) = ValueTask< 'TResult> ( Task.FromException< 'TResult> `` exception `` )
155-
156238
157- #if NET5_ 0_ OR_ GREATER
158- /// Creates a ValueTask from a value
159- let result value = ValueTask.FromResult value
160- #endif
161239#endif
0 commit comments