@@ -7,6 +7,7 @@ namespace FSharpPlus
7
7
module Task =
8
8
9
9
open System
10
+ open System.Threading
10
11
open System.Threading .Tasks
11
12
12
13
let private (| Canceled | Faulted | Completed |) ( t : Task < 'a >) =
@@ -135,26 +136,100 @@ module Task =
135
136
tcs.Task
136
137
137
138
/// <summary>Creates a task workflow from two workflows 'x' and 'y', mapping its results with 'f'.</summary>
138
- /// <remarks>Similar to lift2 but although workflows are started in sequence they might end independently in different order.</remarks>
139
- /// <param name="f">The mapping function.</param>
140
- /// <param name="x">First task workflow.</param>
141
- /// <param name="y">Second task workflow.</param>
142
- let map2 f x y = task {
143
- let! x ' = x
144
- let! y ' = y
145
- return f x' y' }
139
+ /// <remarks>Similar to lift2 but although workflows are started in sequence they might end independently in different order
140
+ /// and all errors are collected.
141
+ /// </remarks>
142
+ /// <param name="mapper">The mapping function.</param>
143
+ /// <param name="task1">First task workflow.</param>
144
+ /// <param name="task2">Second task workflow.</param>
145
+ let map2 mapper ( task1 : Task < 'T1 >) ( task2 : Task < 'T2 >) : Task < 'U > =
146
+ if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion then
147
+ try Task.FromResult ( mapper task1.Result task2.Result)
148
+ with e ->
149
+ let tcs = TaskCompletionSource<_> ()
150
+ tcs.SetException e
151
+ tcs.Task
152
+ else
153
+ let tcs = TaskCompletionSource<_> ()
154
+ let r1 = ref Unchecked.defaultof<_>
155
+ let r2 = ref Unchecked.defaultof<_>
156
+ let mutable cancelled = false
157
+ let failures = [| IReadOnlyCollection.empty; IReadOnlyCollection.empty|]
158
+ let pending = ref 2
159
+
160
+ let trySet () =
161
+ if Interlocked.Decrement pending = 0 then
162
+ let noFailures = Array.forall IReadOnlyCollection.isEmpty failures
163
+ if noFailures && not cancelled then
164
+ try tcs.SetResult ( mapper r1.Value r2.Value)
165
+ with e -> tcs.SetException e
166
+ elif noFailures then tcs.SetCanceled ()
167
+ else tcs.SetException ( failures |> Seq.map AggregateException |> Seq.reduce Exception.add) .InnerExceptions
168
+
169
+ let k ( v : ref < 'k >) i t =
170
+ match t with
171
+ | Canceled -> cancelled <- true
172
+ | Faulted e -> failures[ i] <- e.InnerExceptions
173
+ | Completed r -> v.Value <- r
174
+ trySet ()
175
+
176
+ if task1.IsCompleted && task2.IsCompleted then
177
+ task1 |> k r1 0
178
+ task2 |> k r2 1
179
+ else
180
+ task1.ContinueWith ( k r1 0 ) |> ignore
181
+ task2.ContinueWith ( k r2 1 ) |> ignore
182
+ tcs.Task
146
183
147
184
/// <summary>Creates a task workflow from three workflows 'x', 'y' and z, mapping its results with 'f'.</summary>
148
- /// <remarks>Similar to lift3 but although workflows are started in sequence they might end independently in different order.</remarks>
149
- /// <param name="f">The mapping function.</param>
150
- /// <param name="x">First task workflow.</param>
151
- /// <param name="y">Second task workflow.</param>
152
- /// <param name="z">Third task workflow.</param>
153
- let map3 f x y z = task {
154
- let! x ' = x
155
- let! y ' = y
156
- let! z ' = z
157
- return f x' y' z' }
185
+ /// <remarks>Similar to lift3 but although workflows are started in sequence they might end independently in different order
186
+ /// and all errors are collected.
187
+ /// </remarks>
188
+ /// <param name="mapper">The mapping function.</param>
189
+ /// <param name="task1">First task workflow.</param>
190
+ /// <param name="task2">Second task workflow.</param>
191
+ /// <param name="task3">Third task workflow.</param>
192
+ let map3 mapper ( task1 : Task < 'T1 >) ( task2 : Task < 'T2 >) ( task3 : Task < 'T3 >) : Task < 'U > =
193
+ if task1.Status = TaskStatus.RanToCompletion && task2.Status = TaskStatus.RanToCompletion && task3.Status = TaskStatus.RanToCompletion then
194
+ try Task.FromResult ( mapper task1.Result task2.Result task3.Result)
195
+ with e ->
196
+ let tcs = TaskCompletionSource<_> ()
197
+ tcs.SetException e
198
+ tcs.Task
199
+ else
200
+ let tcs = TaskCompletionSource<_> ()
201
+ let r1 = ref Unchecked.defaultof<_>
202
+ let r2 = ref Unchecked.defaultof<_>
203
+ let r3 = ref Unchecked.defaultof<_>
204
+ let mutable cancelled = false
205
+ let failures = [| IReadOnlyCollection.empty< exn>; IReadOnlyCollection.empty; IReadOnlyCollection.empty|]
206
+ let pending = ref 3
207
+
208
+ let trySet () =
209
+ if Interlocked.Decrement pending = 0 then
210
+ let noFailures = Array.forall isNull failures
211
+ if noFailures && not cancelled then
212
+ try tcs.SetResult ( mapper r1.Value r2.Value r3.Value)
213
+ with e -> tcs.SetException e
214
+ elif noFailures then tcs.SetCanceled ()
215
+ else tcs.SetException ( failures |> Seq.concat |> Seq.fold Exception.add ( AggregateException ())) .InnerExceptions
216
+
217
+ let k ( v : ref < 'k >) i t =
218
+ match t with
219
+ | Canceled -> cancelled <- true
220
+ | Faulted e -> failures[ i] <- e.InnerExceptions
221
+ | Completed r -> v.Value <- r
222
+ trySet ()
223
+
224
+ if task1.IsCompleted && task2.IsCompleted && task3.IsCompleted then
225
+ task1 |> k r1 0
226
+ task2 |> k r2 1
227
+ task3 |> k r3 2
228
+ else
229
+ task1.ContinueWith ( k r1 0 ) |> ignore
230
+ task2.ContinueWith ( k r2 1 ) |> ignore
231
+ task3.ContinueWith ( k r3 2 ) |> ignore
232
+ tcs.Task
158
233
159
234
/// <summary>Creates a task workflow that is the result of applying the resulting function of a task workflow
160
235
/// to the resulting value of another task workflow</summary>
@@ -242,11 +317,16 @@ module Task =
242
317
tcs.Task
243
318
244
319
/// <summary>Creates a task workflow from two workflows 'x' and 'y', tupling its results.</summary>
245
- /// <remarks>Similar to zipSequentially but although workflows are started in sequence they might end independently in different order.</remarks>
246
- let zip x y = task {
247
- let! x ' = x
248
- let! y ' = y
249
- return x', y' }
320
+ /// <remarks>Similar to zipSequentially but although workflows are started in sequence they might end independently in different order
321
+ /// and all errors are collected.
322
+ /// </remarks>
323
+ let zip ( task1 : Task < 'T1 >) ( task2 : Task < 'T2 >) = map2 ( fun x y -> x, y) task1 task2
324
+
325
+ /// <summary>Creates a task workflow from two workflows 'x', 'y' and 'z', tupling its results.</summary>
326
+ /// <remarks>Similar to zipSequentially but although workflows are started in sequence they might end independently in different order
327
+ /// and all errors are collected.
328
+ /// </remarks>
329
+ let zip3 ( task1 : Task < 'T1 >) ( task2 : Task < 'T2 >) ( task3 : Task < 'T3 >) = map3 ( fun x y z -> x, y, z) task1 task2 task3
250
330
251
331
/// Flattens two nested tasks into one.
252
332
let join ( source : Task < Task < 'T >>) : Task < 'T > = source.Unwrap()
0 commit comments