Skip to content

Commit

Permalink
Merge pull request #7 from TheAngryByrd/6-parallelAsync
Browse files Browse the repository at this point in the history
Adds parallelAsync builder
  • Loading branch information
TheAngryByrd committed Mar 23, 2022
2 parents 07f4621 + 6398dee commit 3420057
Show file tree
Hide file tree
Showing 14 changed files with 1,132 additions and 321 deletions.
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,103 @@ This library contains additional [computation expressions](https://docs.microsof

- `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).

- `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).

## How

### ColdTask

Short example:

```fsharp
open IcedTasks
let coldTask_dont_start_immediately = task {
let mutable someValue = null
let fooColdTask = coldTask { someValue <- 42 }
do! Async.Sleep(100)
// ColdTasks will not execute until they are called, similar to how Async works
Expect.equal someValue null ""
// Calling fooColdTask will start to execute it
do! fooColdTask ()
Expect.equal someValue 42 ""
}
```

### CancellableTask

Accessing the context's CancellationToken:

1. Binding against `CancellationToken -> Task<_>`

```fsharp
let writeJunkToFile =
let path = Path.GetTempFileName()
cancellableTask {
let junk = Array.zeroCreate bufferSize
use file = File.Create(path)
for i = 1 to manyIterations do
// You can do! directly against a function with the signature of `CancellationToken -> Task<_>` to access the context's `CancellationToken`. This is slightly more performant.
do! fun ct -> file.WriteAsync(junk, 0, junk.Length, ct)
}
```

2. Binding against `CancellableTask.getCancellationToken`

```fsharp
let writeJunkToFile =
let path = Path.GetTempFileName()
cancellableTask {
let junk = Array.zeroCreate bufferSize
use file = File.Create(path)
// You can bind against `CancellableTask.getCancellationToken` to get the current context's `CancellationToken`.
let! ct = CancellableTask.getCancellationToken
for i = 1 to manyIterations do
do! file.WriteAsync(junk, 0, junk.Length, ct)
}
```

Short example:

```fsharp
let executeWriting = task {
// CancellableTask is an alias for `CancellationToken -> Task<_>` so we'll need to pass in a `CancellationToken`.
// For this example we'll use a `CancellationTokenSource` but if you were using something like ASP.NET, passing in `httpContext.RequestAborted` would be appropriate.
use cts = new CancellationTokenSource()
// call writeJunkToFile from our previous example
do! writeJunkToFile cts.Token
}
```

### ParallelAsync

Short example:

```fsharp
open IcedTasks
let exampleHttpCall url = async {
// Pretend we're executing an HttpClient call
return 42
}
let getDataFromAFewSites = parallelAsync {
let! result1 = exampleHttpCall "howManyPlantsDoIOwn"
and! result2 = exampleHttpCall "whatsTheTemperature"
and! result3 = exampleHttpCall "whereIsMyPhone"
// Do something meaningful with results
return ()
}
```

---

## Builds
Expand Down
274 changes: 274 additions & 0 deletions benchmarks/FSharpBenchmarks/AsyncBenchmarks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ open System.IO
module Helpers =
let bufferSize = 128
let manyIterations = 1000
let fewerIterations = manyIterations / 10
let syncTask () = Task.FromResult 100
let syncCtTask (ct: CancellationToken) = Task.FromResult 100
let syncTask_async () = async.Return 100
let syncTask_async2 () = Task.FromResult 100
let asyncYield () = Async.Sleep(0)
let asyncYieldLong () = Async.Sleep(10)
let asyncTask () = Task.Yield()
let asyncTaskCt (ct: CancellationToken) = Task.Yield()

Expand Down Expand Up @@ -565,3 +567,275 @@ type AsyncBenchmarks() =
for i in 1..manyIterations do
(tenBindAsync_cancellableTask_bindCancellableTask () (CancellationToken.None))
.Wait()




module AsyncExns =

type AsyncBuilder with
member inline _.MergeSources(t1: Async<'T>, t2: Async<'T1>) =
// async {
// let! t1r = t1
// let! t2r = t2
// return t1r,t2r
// }
async.Bind(t1, (fun t1r -> async.Bind(t2, (fun t2r -> async.Return(t1r, t2r)))))

open AsyncExns

[<MemoryDiagnoser>]
[<GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)>]
[<CategoriesColumn>]
type ParallelAsyncBenchmarks() =


[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
member _.AsyncBuilder_sync() =

for i in 1..manyIterations do
Helpers.tenBindSync_async ()
|> Async.RunSynchronously
|> ignore


[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
member _.AsyncBuilder_sync_applicative_overhead() =

for i in 1..manyIterations do
async {
let! res1 = syncTask_async ()
and! res2 = syncTask_async ()
and! res3 = syncTask_async ()
and! res4 = syncTask_async ()
and! res5 = syncTask_async ()
and! res6 = syncTask_async ()
and! res7 = syncTask_async ()
and! res8 = syncTask_async ()
and! res9 = syncTask_async ()
and! res10 = syncTask_async ()

return
res1
+ res2
+ res3
+ res4
+ res5
+ res6
+ res7
+ res8
+ res9
+ res10
}
|> Async.RunSynchronously
|> ignore



[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
member _.ParallelAsyncBuilderUsingStartChild_sync() =

for i in 1..manyIterations do
parallelAsyncUsingStartChild {
let! res1 = syncTask_async ()
and! res2 = syncTask_async ()
and! res3 = syncTask_async ()
and! res4 = syncTask_async ()
and! res5 = syncTask_async ()
and! res6 = syncTask_async ()
and! res7 = syncTask_async ()
and! res8 = syncTask_async ()
and! res9 = syncTask_async ()
and! res10 = syncTask_async ()

return
res1
+ res2
+ res3
+ res4
+ res5
+ res6
+ res7
+ res8
+ res9
+ res10
}
|> Async.RunSynchronously
|> ignore


[<BenchmarkCategory("NonAsyncBinds"); Benchmark>]
member _.ParallelAsyncBuilderUsingStartImmediateAsTask_sync() =
for i in 1..manyIterations do
parallelAsyncUsingStartImmediateAsTask {
let! res1 = syncTask_async ()
and! res2 = syncTask_async ()
and! res3 = syncTask_async ()
and! res4 = syncTask_async ()
and! res5 = syncTask_async ()
and! res6 = syncTask_async ()
and! res7 = syncTask_async ()
and! res8 = syncTask_async ()
and! res9 = syncTask_async ()
and! res10 = syncTask_async ()

return
res1
+ res2
+ res3
+ res4
+ res5
+ res6
+ res7
+ res8
+ res9
+ res10
}
|> Async.RunSynchronously
|> ignore

[<BenchmarkCategory("AsyncBinds"); Benchmark>]
member _.AsyncBuilder_async() =

for i in 1..manyIterations do
Helpers.tenBindAsync_async ()
|> Async.RunSynchronously


[<BenchmarkCategory("AsyncBinds"); Benchmark>]
member _.AsyncBuilder_async_applicative_overhead() =

for i in 1..manyIterations do
async {
let! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
return ()
}
|> Async.RunSynchronously


[<BenchmarkCategory("AsyncBinds"); Benchmark>]
member _.ParallelAsyncBuilderUsingStartChild_async() =

for i in 1..manyIterations do
parallelAsyncUsingStartChild {
let! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
return ()
}
|> Async.RunSynchronously

[<BenchmarkCategory("AsyncBinds"); Benchmark>]
member _.ParallelAsyncBuilderUsingStartImmediateAsTask_async() =

for i in 1..manyIterations do
parallelAsyncUsingStartImmediateAsTask {

let! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
and! _ = asyncYield ()
return ()
}
|> Async.RunSynchronously


[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
member _.AsyncBuilder_async_long() =

for i in 1..fewerIterations do
async {
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
let! _ = asyncYieldLong ()
return ()
}
|> Async.RunSynchronously


[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
member _.AsyncBuilder_async_long_applicative_overhead() =

for i in 1..fewerIterations do
async {
let! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
return ()
}
|> Async.RunSynchronously

[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
member _.ParallelAsyncBuilderUsingStartChild_async_long() =

for i in 1..fewerIterations do
parallelAsyncUsingStartChild {
let! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
return ()
}
|> Async.RunSynchronously

[<BenchmarkCategory("AsyncBindsLong"); Benchmark>]
member _.ParallelAsyncBuilderUsingStartImmediateAsTask_async_long() =

for i in 1..fewerIterations do
parallelAsyncUsingStartImmediateAsTask {

let! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
and! _ = asyncYieldLong ()
return ()
}
|> Async.RunSynchronously
Loading

0 comments on commit 3420057

Please sign in to comment.