Skip to content

Commit 3420057

Browse files
authored
Merge pull request #7 from TheAngryByrd/6-parallelAsync
Adds parallelAsync builder
2 parents 07f4621 + 6398dee commit 3420057

14 files changed

+1132
-321
lines changed

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,103 @@ This library contains additional [computation expressions](https://docs.microsof
88

99
- `CancellableTask<'T>` - Alias for `CancellationToken -> Task<'T>`. Allows for lazy evaluation (also known as Cold) of the tasks, similar to [F#'s Async being cold](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/async#core-concepts-of-async). Additionally, allows for flowing a [CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-6.0) through the computation, similar to [F#'s Async cancellation support](http://tomasp.net/blog/async-csharp-differences.aspx/#:~:text=In%20F%23%20asynchronous%20workflows%2C%20the,and%20everything%20will%20work%20automatically).
1010

11+
- `ParallelAsync<'T>`. Utilizes the [applicative syntax](https://docs.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-50#applicative-computation-expressions) to allow parallel execution of [Async<'T> expressions](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/async-expressions).
12+
13+
## How
14+
15+
### ColdTask
16+
17+
Short example:
18+
19+
```fsharp
20+
open IcedTasks
21+
22+
let coldTask_dont_start_immediately = task {
23+
let mutable someValue = null
24+
let fooColdTask = coldTask { someValue <- 42 }
25+
do! Async.Sleep(100)
26+
// ColdTasks will not execute until they are called, similar to how Async works
27+
Expect.equal someValue null ""
28+
// Calling fooColdTask will start to execute it
29+
do! fooColdTask ()
30+
Expect.equal someValue 42 ""
31+
}
32+
33+
```
34+
35+
### CancellableTask
36+
37+
Accessing the context's CancellationToken:
38+
39+
1. Binding against `CancellationToken -> Task<_>`
40+
41+
```fsharp
42+
let writeJunkToFile =
43+
let path = Path.GetTempFileName()
44+
45+
cancellableTask {
46+
let junk = Array.zeroCreate bufferSize
47+
use file = File.Create(path)
48+
49+
for i = 1 to manyIterations do
50+
// You can do! directly against a function with the signature of `CancellationToken -> Task<_>` to access the context's `CancellationToken`. This is slightly more performant.
51+
do! fun ct -> file.WriteAsync(junk, 0, junk.Length, ct)
52+
}
53+
```
54+
55+
2. Binding against `CancellableTask.getCancellationToken`
56+
57+
```fsharp
58+
let writeJunkToFile =
59+
let path = Path.GetTempFileName()
60+
61+
cancellableTask {
62+
let junk = Array.zeroCreate bufferSize
63+
use file = File.Create(path)
64+
// You can bind against `CancellableTask.getCancellationToken` to get the current context's `CancellationToken`.
65+
let! ct = CancellableTask.getCancellationToken
66+
for i = 1 to manyIterations do
67+
do! file.WriteAsync(junk, 0, junk.Length, ct)
68+
}
69+
```
70+
71+
Short example:
72+
73+
```fsharp
74+
let executeWriting = task {
75+
// CancellableTask is an alias for `CancellationToken -> Task<_>` so we'll need to pass in a `CancellationToken`.
76+
// For this example we'll use a `CancellationTokenSource` but if you were using something like ASP.NET, passing in `httpContext.RequestAborted` would be appropriate.
77+
use cts = new CancellationTokenSource()
78+
// call writeJunkToFile from our previous example
79+
do! writeJunkToFile cts.Token
80+
}
81+
82+
83+
```
84+
85+
### ParallelAsync
86+
87+
Short example:
88+
89+
```fsharp
90+
open IcedTasks
91+
92+
let exampleHttpCall url = async {
93+
// Pretend we're executing an HttpClient call
94+
return 42
95+
}
96+
97+
let getDataFromAFewSites = parallelAsync {
98+
let! result1 = exampleHttpCall "howManyPlantsDoIOwn"
99+
and! result2 = exampleHttpCall "whatsTheTemperature"
100+
and! result3 = exampleHttpCall "whereIsMyPhone"
101+
102+
// Do something meaningful with results
103+
return ()
104+
}
105+
106+
```
107+
11108
---
12109

13110
## Builds

benchmarks/FSharpBenchmarks/AsyncBenchmarks.fs

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ open System.IO
1616
module Helpers =
1717
let bufferSize = 128
1818
let manyIterations = 1000
19+
let fewerIterations = manyIterations / 10
1920
let syncTask () = Task.FromResult 100
2021
let syncCtTask (ct: CancellationToken) = Task.FromResult 100
2122
let syncTask_async () = async.Return 100
2223
let syncTask_async2 () = Task.FromResult 100
2324
let asyncYield () = Async.Sleep(0)
25+
let asyncYieldLong () = Async.Sleep(10)
2426
let asyncTask () = Task.Yield()
2527
let asyncTaskCt (ct: CancellationToken) = Task.Yield()
2628

@@ -565,3 +567,275 @@ type AsyncBenchmarks() =
565567
for i in 1..manyIterations do
566568
(tenBindAsync_cancellableTask_bindCancellableTask () (CancellationToken.None))
567569
.Wait()
570+
571+
572+
573+
574+
module AsyncExns =
575+
576+
type AsyncBuilder with
577+
member inline _.MergeSources(t1: Async<'T>, t2: Async<'T1>) =
578+
// async {
579+
// let! t1r = t1
580+
// let! t2r = t2
581+
// return t1r,t2r
582+
// }
583+
async.Bind(t1, (fun t1r -> async.Bind(t2, (fun t2r -> async.Return(t1r, t2r)))))
584+
585+
open AsyncExns
586+
587+
[<MemoryDiagnoser>]
588+
[<GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)>]
589+
[<CategoriesColumn>]
590+
type ParallelAsyncBenchmarks() =
591+
592+
593+
[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
594+
member _.AsyncBuilder_sync() =
595+
596+
for i in 1..manyIterations do
597+
Helpers.tenBindSync_async ()
598+
|> Async.RunSynchronously
599+
|> ignore
600+
601+
602+
[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
603+
member _.AsyncBuilder_sync_applicative_overhead() =
604+
605+
for i in 1..manyIterations do
606+
async {
607+
let! res1 = syncTask_async ()
608+
and! res2 = syncTask_async ()
609+
and! res3 = syncTask_async ()
610+
and! res4 = syncTask_async ()
611+
and! res5 = syncTask_async ()
612+
and! res6 = syncTask_async ()
613+
and! res7 = syncTask_async ()
614+
and! res8 = syncTask_async ()
615+
and! res9 = syncTask_async ()
616+
and! res10 = syncTask_async ()
617+
618+
return
619+
res1
620+
+ res2
621+
+ res3
622+
+ res4
623+
+ res5
624+
+ res6
625+
+ res7
626+
+ res8
627+
+ res9
628+
+ res10
629+
}
630+
|> Async.RunSynchronously
631+
|> ignore
632+
633+
634+
635+
[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
636+
member _.ParallelAsyncBuilderUsingStartChild_sync() =
637+
638+
for i in 1..manyIterations do
639+
parallelAsyncUsingStartChild {
640+
let! res1 = syncTask_async ()
641+
and! res2 = syncTask_async ()
642+
and! res3 = syncTask_async ()
643+
and! res4 = syncTask_async ()
644+
and! res5 = syncTask_async ()
645+
and! res6 = syncTask_async ()
646+
and! res7 = syncTask_async ()
647+
and! res8 = syncTask_async ()
648+
and! res9 = syncTask_async ()
649+
and! res10 = syncTask_async ()
650+
651+
return
652+
res1
653+
+ res2
654+
+ res3
655+
+ res4
656+
+ res5
657+
+ res6
658+
+ res7
659+
+ res8
660+
+ res9
661+
+ res10
662+
}
663+
|> Async.RunSynchronously
664+
|> ignore
665+
666+
667+
[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
668+
member _.ParallelAsyncBuilderUsingStartImmediateAsTask_sync() =
669+
for i in 1..manyIterations do
670+
parallelAsyncUsingStartImmediateAsTask {
671+
let! res1 = syncTask_async ()
672+
and! res2 = syncTask_async ()
673+
and! res3 = syncTask_async ()
674+
and! res4 = syncTask_async ()
675+
and! res5 = syncTask_async ()
676+
and! res6 = syncTask_async ()
677+
and! res7 = syncTask_async ()
678+
and! res8 = syncTask_async ()
679+
and! res9 = syncTask_async ()
680+
and! res10 = syncTask_async ()
681+
682+
return
683+
res1
684+
+ res2
685+
+ res3
686+
+ res4
687+
+ res5
688+
+ res6
689+
+ res7
690+
+ res8
691+
+ res9
692+
+ res10
693+
}
694+
|> Async.RunSynchronously
695+
|> ignore
696+
697+
[<BenchmarkCategory("AsyncBinds"); Benchmark>]
698+
member _.AsyncBuilder_async() =
699+
700+
for i in 1..manyIterations do
701+
Helpers.tenBindAsync_async ()
702+
|> Async.RunSynchronously
703+
704+
705+
[<BenchmarkCategory("AsyncBinds"); Benchmark>]
706+
member _.AsyncBuilder_async_applicative_overhead() =
707+
708+
for i in 1..manyIterations do
709+
async {
710+
let! _ = asyncYield ()
711+
and! _ = asyncYield ()
712+
and! _ = asyncYield ()
713+
and! _ = asyncYield ()
714+
and! _ = asyncYield ()
715+
and! _ = asyncYield ()
716+
and! _ = asyncYield ()
717+
and! _ = asyncYield ()
718+
and! _ = asyncYield ()
719+
and! _ = asyncYield ()
720+
return ()
721+
}
722+
|> Async.RunSynchronously
723+
724+
725+
[<BenchmarkCategory("AsyncBinds"); Benchmark>]
726+
member _.ParallelAsyncBuilderUsingStartChild_async() =
727+
728+
for i in 1..manyIterations do
729+
parallelAsyncUsingStartChild {
730+
let! _ = asyncYield ()
731+
and! _ = asyncYield ()
732+
and! _ = asyncYield ()
733+
and! _ = asyncYield ()
734+
and! _ = asyncYield ()
735+
and! _ = asyncYield ()
736+
and! _ = asyncYield ()
737+
and! _ = asyncYield ()
738+
and! _ = asyncYield ()
739+
and! _ = asyncYield ()
740+
return ()
741+
}
742+
|> Async.RunSynchronously
743+
744+
[<BenchmarkCategory("AsyncBinds"); Benchmark>]
745+
member _.ParallelAsyncBuilderUsingStartImmediateAsTask_async() =
746+
747+
for i in 1..manyIterations do
748+
parallelAsyncUsingStartImmediateAsTask {
749+
750+
let! _ = asyncYield ()
751+
and! _ = asyncYield ()
752+
and! _ = asyncYield ()
753+
and! _ = asyncYield ()
754+
and! _ = asyncYield ()
755+
and! _ = asyncYield ()
756+
and! _ = asyncYield ()
757+
and! _ = asyncYield ()
758+
and! _ = asyncYield ()
759+
and! _ = asyncYield ()
760+
return ()
761+
}
762+
|> Async.RunSynchronously
763+
764+
765+
[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
766+
member _.AsyncBuilder_async_long() =
767+
768+
for i in 1..fewerIterations do
769+
async {
770+
let! _ = asyncYieldLong ()
771+
let! _ = asyncYieldLong ()
772+
let! _ = asyncYieldLong ()
773+
let! _ = asyncYieldLong ()
774+
let! _ = asyncYieldLong ()
775+
let! _ = asyncYieldLong ()
776+
let! _ = asyncYieldLong ()
777+
let! _ = asyncYieldLong ()
778+
let! _ = asyncYieldLong ()
779+
let! _ = asyncYieldLong ()
780+
return ()
781+
}
782+
|> Async.RunSynchronously
783+
784+
785+
[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
786+
member _.AsyncBuilder_async_long_applicative_overhead() =
787+
788+
for i in 1..fewerIterations do
789+
async {
790+
let! _ = asyncYieldLong ()
791+
and! _ = asyncYieldLong ()
792+
and! _ = asyncYieldLong ()
793+
and! _ = asyncYieldLong ()
794+
and! _ = asyncYieldLong ()
795+
and! _ = asyncYieldLong ()
796+
and! _ = asyncYieldLong ()
797+
and! _ = asyncYieldLong ()
798+
and! _ = asyncYieldLong ()
799+
and! _ = asyncYieldLong ()
800+
return ()
801+
}
802+
|> Async.RunSynchronously
803+
804+
[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
805+
member _.ParallelAsyncBuilderUsingStartChild_async_long() =
806+
807+
for i in 1..fewerIterations do
808+
parallelAsyncUsingStartChild {
809+
let! _ = asyncYieldLong ()
810+
and! _ = asyncYieldLong ()
811+
and! _ = asyncYieldLong ()
812+
and! _ = asyncYieldLong ()
813+
and! _ = asyncYieldLong ()
814+
and! _ = asyncYieldLong ()
815+
and! _ = asyncYieldLong ()
816+
and! _ = asyncYieldLong ()
817+
and! _ = asyncYieldLong ()
818+
and! _ = asyncYieldLong ()
819+
return ()
820+
}
821+
|> Async.RunSynchronously
822+
823+
[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
824+
member _.ParallelAsyncBuilderUsingStartImmediateAsTask_async_long() =
825+
826+
for i in 1..fewerIterations do
827+
parallelAsyncUsingStartImmediateAsTask {
828+
829+
let! _ = asyncYieldLong ()
830+
and! _ = asyncYieldLong ()
831+
and! _ = asyncYieldLong ()
832+
and! _ = asyncYieldLong ()
833+
and! _ = asyncYieldLong ()
834+
and! _ = asyncYieldLong ()
835+
and! _ = asyncYieldLong ()
836+
and! _ = asyncYieldLong ()
837+
and! _ = asyncYieldLong ()
838+
and! _ = asyncYieldLong ()
839+
return ()
840+
}
841+
|> Async.RunSynchronously

0 commit comments

Comments
 (0)