Header menu logo IcedTasks

A brief introduction to IcedTasks

One of F# Async's benefits is having cancellation support built in from the beginning. However Task's do not and must explicitly be passed a CancellationToken. This is a problem because it creates a lot of verbosity and boilerplate code. IcedTasks is a library that aims to solve this problem by providing a cancellableTask computation expression that can be used to create Tasks that can be cancelled, passing cancellation token along to other cancellableTasks implicitly. And when calling code that takes a CancellationToken, you can easily get at that token and pass it along to the method.

Here we'll take a look at a very small sample of how to use IcedTasks to create a cancellableTask that can be cancelled by a CancellationToken.

#r "nuget: IcedTasks"

open System
open System.Threading
open System.Threading.Tasks

open IcedTasks

/// Helper for unning a Task synchronously
module Task =
    let RunSynchronously (task: Task) = task.GetAwaiter().GetResult()

// Stand in for some database call, like Npgsql
type Person = { Name: string; Age: int }

type Database =
    static member Get<'a>(query, queryParams, cancellationToken: CancellationToken) =
        task {
            do! Task.Delay(1000, cancellationToken)
            return { Name = "Foo"; Age = 42 }
        }

// Stand in for some web call
type WebCall =
    static member HttpGet(route, cancellationToken: CancellationToken) =
        task {
            do! Task.Delay(1000, cancellationToken)
            return { Name = "Foo"; Age = 42 }
        }

let someOtherBusinessLogic (person: Person) =
    cancellableTask {
        let! ct = CancellableTask.getCancellationToken () // A helper to get the current CancellationToken
        let! result = WebCall.HttpGet("https://example.com", ct)
        return result.Age < 1000 // prevent vampires from using our app
    }

let cacheItem (key: string) value =
    async {
        let! ct = Async.CancellationToken // This token will come from the cancellable task

        let! result =
            Database.Get("SELECT foo FROM bar where baz = @0", [ "@0", key ], ct)
            |> Async.AwaitTask

        return ()
    }

let businessLayerCall someParameter =
    cancellableTask {
        // use a lamdbda to get the cancellableTask's current CancellationToken
        // then bind against it like you normally would in any other computation expression
        let! result =
            fun cancellationToken ->
                Database.Get(
                    "SELECT foo FROM bar where baz = @0",
                    [ "@0", someParameter ],
                    cancellationToken
                )

        // This will implicitly pass the CancellationToken along to the next cancellableTask
        let! notVampire = someOtherBusinessLogic result

        // This will implicitly pass the CancellationToken along to async computation expressions as well
        do! cacheItem "buzz" result

        // Conduct some business logic
        if
            result.Age > 18
            && notVampire
        then
            return Some result
        else
            return None
    }


// Now we can use our businessLayerCall like any other Task
let tokenSource = new CancellationTokenSource()
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5)) // Should cancel our database call

// businessLayerCall is really a string -> CancellationToken -> Task<Option<Person>> so we want to pass in the cancellation token.
businessLayerCall "buzz" tokenSource.Token
|> Task.RunSynchronously

For examples on how to use this technique in other contexts checkout:

namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace IcedTasks
Multiple items
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable + 1 overload member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 5 overloads ...
<summary>Represents an asynchronous operation.</summary>

--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> + 1 overload member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 4 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>


--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task

--------------------
Task(``function`` : Func<'TResult>) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
val RunSynchronously: task: Task -> unit
val task: Task
Task.GetAwaiter() : Runtime.CompilerServices.TaskAwaiter
type Person = { Name: string Age: int }
Multiple items
val string: value: 'T -> string

--------------------
type string = String
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
'a
val query: string
val queryParams: (string * string) list
val cancellationToken: CancellationToken
Multiple items
[<Struct>] type CancellationToken = new: canceled: bool -> unit member Equals: other: obj -> bool + 1 overload member GetHashCode: unit -> int member Register: callback: Action -> CancellationTokenRegistration + 4 overloads member ThrowIfCancellationRequested: unit -> unit member UnsafeRegister: callback: Action<obj,CancellationToken> * state: obj -> CancellationTokenRegistration + 1 overload static member (<>) : left: CancellationToken * right: CancellationToken -> bool static member (=) : left: CancellationToken * right: CancellationToken -> bool member CanBeCanceled: bool member IsCancellationRequested: bool ...
<summary>Propagates notification that operations should be canceled.</summary>

--------------------
CancellationToken ()
CancellationToken(canceled: bool) : CancellationToken
val task: TaskBuilder
Task.Delay(delay: TimeSpan) : Task
Task.Delay(millisecondsDelay: int) : Task
Task.Delay(delay: TimeSpan, timeProvider: TimeProvider) : Task
Task.Delay(delay: TimeSpan, cancellationToken: CancellationToken) : Task
Task.Delay(millisecondsDelay: int, cancellationToken: CancellationToken) : Task
Task.Delay(delay: TimeSpan, timeProvider: TimeProvider, cancellationToken: CancellationToken) : Task
val route: 'a
val someOtherBusinessLogic: person: Person -> CancellableTask<bool>
val person: Person
val cancellableTask: CancellableTaskBuilder
<summary> Builds a cancellableTask using computation expression syntax. </summary>
val ct: CancellationToken
Multiple items
module CancellableTask from IcedTasks.CancellableTasks

--------------------
type CancellableTask = CancellationToken -> Task
<summary> CancellationToken -&gt; Task </summary>

--------------------
type CancellableTask<'T> = CancellationToken -> Task<'T>
<summary> CancellationToken -&gt; Task&lt;'T&gt; </summary>
val getCancellationToken: unit -> ct: CancellationToken -> ValueTask<CancellationToken>
<summary>Gets the default cancellation token for executing computations.</summary>
<returns>The default CancellationToken.</returns>
<category index="3">Cancellation and Exceptions</category>
<example id="default-cancellation-token-1"><code lang="F#"> use tokenSource = new CancellationTokenSource() let primes = [ 2; 3; 5; 7; 11 ] for i in primes do let computation = cancellableTask { let! cancellationToken = CancellableTask.getCancellationToken() do! Task.Delay(i * 1000, cancellationToken) printfn $"{i}" } computation tokenSource.Token |&gt; ignore Thread.Sleep(6000) tokenSource.Cancel() printfn "Tasks Finished" </code> This will print "2" 2 seconds from start, "3" 3 seconds from start, "5" 5 seconds from start, cease computation and then followed by "Tasks Finished". </example>
val result: Person
type WebCall = static member HttpGet: route: 'a * cancellationToken: CancellationToken -> Task<Person>
static member WebCall.HttpGet: route: 'a * cancellationToken: CancellationToken -> Task<Person>
Person.Age: int
val cacheItem: key: string -> value: 'a -> Async<unit>
val key: string
val value: 'a
val async: AsyncBuilder
Multiple items
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>
property Async.CancellationToken: Async<CancellationToken> with get
type Database = static member Get: query: string * queryParams: (string * string) list * cancellationToken: CancellationToken -> Task<Person>
static member Database.Get: query: string * queryParams: (string * string) list * cancellationToken: CancellationToken -> Task<Person>
static member Async.AwaitTask: task: Task -> Async<unit>
static member Async.AwaitTask: task: Task<'T> -> Async<'T>
val businessLayerCall: someParameter: string -> CancellableTask<Person option>
val someParameter: string
val notVampire: bool
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val tokenSource: CancellationTokenSource
Multiple items
type CancellationTokenSource = interface IDisposable new: unit -> unit + 3 overloads member Cancel: unit -> unit + 1 overload member CancelAfter: millisecondsDelay: int -> unit + 1 overload member CancelAsync: unit -> Task member Dispose: unit -> unit member TryReset: unit -> bool static member CreateLinkedTokenSource: token: CancellationToken -> CancellationTokenSource + 3 overloads member IsCancellationRequested: bool member Token: CancellationToken
<summary>Signals to a <see cref="T:System.Threading.CancellationToken" /> that it should be canceled.</summary>

--------------------
CancellationTokenSource() : CancellationTokenSource
CancellationTokenSource(millisecondsDelay: int) : CancellationTokenSource
CancellationTokenSource(delay: TimeSpan) : CancellationTokenSource
CancellationTokenSource(delay: TimeSpan, timeProvider: TimeProvider) : CancellationTokenSource
CancellationTokenSource.CancelAfter(delay: TimeSpan) : unit
CancellationTokenSource.CancelAfter(millisecondsDelay: int) : unit
Multiple items
[<Struct>] type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 4 overloads member Add: ts: TimeSpan -> TimeSpan member CompareTo: value: obj -> int + 1 overload member Divide: divisor: float -> TimeSpan + 1 overload member Duration: unit -> TimeSpan member Equals: value: obj -> bool + 2 overloads member GetHashCode: unit -> int member Multiply: factor: float -> TimeSpan member Negate: unit -> TimeSpan member Subtract: ts: TimeSpan -> TimeSpan ...
<summary>Represents a time interval.</summary>

--------------------
TimeSpan ()
TimeSpan(ticks: int64) : TimeSpan
TimeSpan(hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int) : TimeSpan
TimeSpan.FromSeconds(value: float) : TimeSpan
TimeSpan.FromSeconds(seconds: int64) : TimeSpan
TimeSpan.FromSeconds(seconds: int64, ?milliseconds: int64, ?microseconds: int64) : TimeSpan
property CancellationTokenSource.Token: CancellationToken with get
<summary>Gets the <see cref="T:System.Threading.CancellationToken" /> associated with this <see cref="T:System.Threading.CancellationTokenSource" />.</summary>
<exception cref="T:System.ObjectDisposedException">The token source has been disposed.</exception>
<returns>The <see cref="T:System.Threading.CancellationToken" /> associated with this <see cref="T:System.Threading.CancellationTokenSource" />.</returns>

Type something to start searching.