Header menu logo IcedTasks

How to use unit builders for non-generic task APIs

Use the unit builders when an API requires a non-generic Task or ValueTask.

The most important case is ValueTask: ValueTask<'T> is not a subtype of ValueTask. For example, IAsyncDisposable.DisposeAsync must return non-generic ValueTask.

You can convert ValueTask<'T> with ValueTask.toUnit, but valueTaskUnit and vTaskUnit build the non-generic shape directly using the non-generic AsyncValueTaskMethodBuilder.

Implement IAsyncDisposable.DisposeAsync

DisposeAsync must return ValueTask, not ValueTask<unit>. Use valueTaskUnit when the cleanup work is naturally written as a computation expression.

type BufferedWriter() =
    let mutable flushed = false

    member _.Flushed = flushed

    member _.FlushAsync() =
        valueTaskUnit {
            do! Task.Delay 1
            flushed <- true
        }

    interface IAsyncDisposable with
        member this.DisposeAsync() = valueTaskUnit { do! this.FlushAsync() }

vTaskUnit is an alias for valueTaskUnit.

let flushWithAlias () : ValueTask = vTaskUnit { do! Task.Delay 1 }

Return non-generic Task

Use taskUnit when an API explicitly wants Task. Task<'T> inherits from Task, but taskUnit states the return shape directly and uses the non-generic task method builder.

type Worker() =
    member _.StopAsync(cancellationToken: CancellationToken) : Task =
        taskUnit { do! Task.Delay(1, cancellationToken) }

Use backgroundTaskUnit when the API wants non-generic Task and the work should avoid the caller's synchronization context. See Use background builders to avoid caller context for the scheduler behavior.

let writeAuditInBackground () : Task = backgroundTaskUnit { do! Task.Delay 1 }

Convert when you already have a generic ValueTask

If you already have a ValueTask<'T>, use ValueTask.toUnit to discard the result and return non-generic ValueTask. Prefer valueTaskUnit when you are authoring the computation and know the API needs ValueTask.

let loadCount () = valueTask { return 42 }

let loadCountAsUnit () : ValueTask =
    loadCount ()
    |> ValueTask.toUnit

Run the examples

These calls make the samples complete and compiler-checked.

let writer = new BufferedWriter()

let disposed =
    (writer :> IAsyncDisposable).DisposeAsync().AsTask().GetAwaiter().GetResult()

let flushed = writer.Flushed
let aliasResult = flushWithAlias().AsTask().GetAwaiter().GetResult()
let stopped = Worker().StopAsync(CancellationToken.None).GetAwaiter().GetResult()
let audit = writeAuditInBackground().GetAwaiter().GetResult()
let loadedAsUnit = loadCountAsUnit().AsTask().GetAwaiter().GetResult()

Choose the unit builder

API needs

Use

Task

taskUnit

Task and background context behavior

backgroundTaskUnit

ValueTask

valueTaskUnit

ValueTask, short alias

vTaskUnit

Use the generic builders when the API should return a meaningful value, such as Task<'T> or ValueTask<'T>.

namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace IcedTasks
Multiple items
type BufferedWriter = interface IAsyncDisposable new: unit -> BufferedWriter member FlushAsync: unit -> ValueTask member Flushed: bool

--------------------
new: unit -> BufferedWriter
val mutable flushed: bool
val valueTaskUnit: ValueTaskUnitBuilder
<summary> Builds a non-generic ValueTask using computation expression syntax. </summary>
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>
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
type IAsyncDisposable = override DisposeAsync: unit -> ValueTask
<summary>Provides a mechanism for releasing unmanaged resources asynchronously.</summary>
val this: BufferedWriter
member BufferedWriter.FlushAsync: unit -> ValueTask
val flushWithAlias: unit -> ValueTask
Multiple items
[<Struct>] type ValueTask = new: source: IValueTaskSource * token: int16 -> unit + 1 overload member AsTask: unit -> Task member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredValueTaskAwaitable member Equals: obj: obj -> bool + 1 overload member GetAwaiter: unit -> ValueTaskAwaiter member GetHashCode: unit -> int member Preserve: unit -> ValueTask static member (<>) : left: ValueTask * right: ValueTask -> bool static member (=) : left: ValueTask * right: ValueTask -> bool static member FromCanceled: cancellationToken: CancellationToken -> ValueTask + 1 overload ...
<summary>Provides an awaitable result of an asynchronous operation.</summary>

--------------------
[<Struct>] type ValueTask<'TResult> = new: source: IValueTaskSource<'TResult> * token: int16 -> unit + 2 overloads member AsTask: unit -> Task<'TResult> member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredValueTaskAwaitable<'TResult> member Equals: obj: obj -> bool + 1 overload member GetAwaiter: unit -> ValueTaskAwaiter<'TResult> member GetHashCode: unit -> int member Preserve: unit -> ValueTask<'TResult> member ToString: unit -> string static member (<>) : left: ValueTask<'TResult> * right: ValueTask<'TResult> -> bool static member (=) : left: ValueTask<'TResult> * right: ValueTask<'TResult> -> bool ...
<summary>Provides a value type that wraps a <see cref="T:System.Threading.Tasks.Task`1" /> and a <typeparamref name="TResult" />, only one of which is used.</summary>
<typeparam name="TResult">The result.</typeparam>


--------------------
ValueTask ()
ValueTask(task: Task) : ValueTask
ValueTask(source: Sources.IValueTaskSource, token: int16) : ValueTask

--------------------
ValueTask ()
ValueTask(task: Task<'TResult>) : ValueTask<'TResult>
ValueTask(result: 'TResult) : ValueTask<'TResult>
ValueTask(source: Sources.IValueTaskSource<'TResult>, token: int16) : ValueTask<'TResult>
val vTaskUnit: ValueTaskUnitBuilder
<summary> Alias for <see cref="F:IcedTasks.ValueTasksUnit.ValueTasksUnit.ValueTaskBuilder.valueTaskUnit" />. </summary>
Multiple items
type Worker = new: unit -> Worker member StopAsync: cancellationToken: CancellationToken -> Task

--------------------
new: unit -> Worker
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 taskUnit: TaskUnitBuilder
<summary> Builds a taskUnit using computation expression syntax. </summary>
val writeAuditInBackground: unit -> Task
val backgroundTaskUnit: BackgroundTaskUnitBuilder
<summary> Builds a taskUnit using computation expression syntax which switches to execute on a background thread if not already doing so. </summary>
val loadCount: unit -> ValueTask<int>
val valueTask: ValueTaskBuilder
<summary> Builds a valueTask using computation expression syntax. </summary>
val loadCountAsUnit: unit -> ValueTask
Multiple items
val toUnit: vtask: ValueTask<'T> -> ValueTask
<summary>Converts a <see cref="T:System.Threading.Tasks.ValueTask`1" /> to its non-generic counterpart.</summary>
<param name="vtask">The ValueTask whose result should be discarded.</param>
<typeparam name="'T">The result type to discard.</typeparam>
<returns>A non-generic ValueTask that completes when <paramref name="vtask" /> completes.</returns>


--------------------
val toUnit: vtask: ValueTask<'T> -> ValueTask
<summary>Converts a <see cref="T:System.Threading.Tasks.ValueTask`1" /> to its non-generic counterpart.</summary>
<param name="vtask">The ValueTask whose result should be discarded.</param>
<typeparam name="'T">The result type to discard.</typeparam>
<returns>A non-generic ValueTask that completes when <paramref name="vtask" /> completes.</returns>
val writer: BufferedWriter
val disposed: unit
val flushed: bool
property BufferedWriter.Flushed: bool with get
val aliasResult: unit
val stopped: unit
property CancellationToken.None: CancellationToken with get
<summary>Returns an empty <see cref="T:System.Threading.CancellationToken" /> value.</summary>
<returns>An empty cancellation token.</returns>
val audit: unit
val loadedAsUnit: unit

Type something to start searching.