What is IcedTasks?
This library contains additional computation expressions for the task CE utilizing the Resumable Code introduced in F# 6.0.
-
ValueTask<'T>
- This utilizes .NET's ValueTask (which is essentially a Discriminated Union of'Value | Task<'Value>
) for possibly better performance in synchronous scenarios. Similar to F#'s Task ExpressionvalueTask
valueTaskUnit
poolingValueTask
-
ColdTask<'T>
- Alias forunit -> Task<'T>
. Allows for lazy evaluation (also known as Cold) of the tasks, similar to F#'s Async being cold.coldTask
-
CancellableTask<'T>
- Alias forCancellationToken -> Task<'T>
. Allows for lazy evaluation (also known as Cold) of the tasks, similar to F#'s Async being cold. Additionally, allows for flowing a CancellationToken through the computation, similar to F#'s Async cancellation support.cancellableTask
cancellableBackgroundTask
-
CancellableValueTask<'T>
- Alias forCancellationToken -> ValueTask<'T>
. Allows for lazy evaluation (also known as Cold) of the tasks, similar to F#'s Async being cold. Additionally, allows for flowing a CancellationToken through the computation, similar to F#'s Async cancellation support.cancellableValueTask
cancellablePoolingValueTask
-
ParallelAsync<'T>
- Utilizes the applicative syntax to allow parallel execution of Async<'T> expressions. See this discussion as to why this is a separate computation expression.parallelAsync
-
AsyncEx<'T>
- Variation of F# async semantics described further below with examples.asyncEx
-
Task<'T>
- Polyfill for fixes to F# Task CE. Can be accessed underIcedTasks.Polyfill
task
backgroundTask
-
Task
- a CE for aTask
that has no return value.taskUnit
backgroundTaskUnit
Why should I use IcedTasks?
AsyncEx
AsyncEx is similar to Async except in the following ways:
-
Allows
use
for IAsyncDisposableopen IcedTasks let fakeDisposable () = { new IAsyncDisposable with member __.DisposeAsync() = ValueTask.CompletedTask } let myAsyncEx = asyncEx { use _ = fakeDisposable () return 42 }
-
Allows
let!/do!
against Tasks/ValueTasks/any Awaitableopen IcedTasks let myAsyncEx = asyncEx { let! _ = task { return 42 } // Task<T> let! _ = valueTask { return 42 } // ValueTask<T> let! _ = Task.Yield() // YieldAwaitable return 42 }
-
When Tasks throw exceptions they will use the behavior described in Async.Await overload (esp. AwaitTask without throwing AggregateException
let data = "lol" let inner = asyncEx { do! task { do! Task.Yield() raise (ArgumentException "foo") return data } :> Task } let outer = asyncEx { try do! inner return () with | :? ArgumentException -> // Should be this exception and not AggregationException return () | ex -> return raise (Exception("Should not throw this type of exception", ex)) }
-
Use IAsyncEnumerable with
for
keyword. This example uses TaskSeq but you can use anyIAsyncEnumerable<T>
.open IcedTasks open FSharp.Control let myAsyncEx = asyncEx { let items = taskSeq { // IAsyncEnumerable<T> yield 42 do! Task.Delay(100) yield 1701 } let mutable sum = 0 for i in items do sum <- sum + i return sum }
For ValueTasks
- F# doesn't currently have a
valueTask
computation expression. Until this PR is merged.
open IcedTasks
let myValueTask = task {
let! theAnswer = valueTask { return 42 }
return theAnswer
}
For Cold & CancellableTasks
- You want control over when your tasks are started
- You want to be able to re-run these executable tasks
- You don't want to pollute your methods/functions with extra CancellationToken parameters
- You want the computation to handle checking cancellation before every bind.
- You want to use the
for
keyword withIAsyncEnumerable<T>
.
ColdTask
Short example:
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 & CancellableValueTask
The examples show cancellableTask
but cancellableValueTask
can be swapped in.
Accessing the context's CancellationToken:
-
Binding against
CancellationToken -> Task<_>
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) }
-
Binding against
CancellableTask.getCancellationToken
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:
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
- When you want to execute multiple asyncs in parallel and wait for all of them to complete.
Short example:
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 ()
}
How do I get started
dotnet add nuget IcedTasks
Who are the maintainers of the project
- @TheAngryByrd
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...
--------------------
type Async<'T>
static member Async.Sleep: millisecondsDueTime: int -> Async<unit>