Table of Contents

Class TaskExtensions

Namespace
VpnHood.Net.Toolkit.Extensions
Assembly
VpnHood.Net.Toolkit.dll

Provides VpnHood's await helpers: Vhc(Task), which configures every await the same way, a helper that blocks on a value task, and helpers that cancel a token source without throwing.

public static class TaskExtensions
Inheritance
TaskExtensions
Inherited Members

Properties

DefaultContinueOnCapturedContext

The single, process-wide continueOnCapturedContext value used by every Vhc(Task) await. Defaults to false, so by default .Vhc() behaves exactly like .ConfigureAwait(false): an await does not capture the current SynchronizationContext, so its continuation is not marshaled back onto it.

public static bool DefaultContinueOnCapturedContext { get; set; }

Property Value

bool

Remarks

This exists so all library/background awaits can be flipped from one place. VpnHood's hot paths run in contexts with no UI and often no SynchronizationContext at all (the iOS Network Extension, the headless VPN service), where continuing on the captured context is at best pointless overhead and at worst a deadlock. Setting this to true once — e.g. for a diagnostic run, or a host that installs a context we deliberately want continuations to flow back onto — changes every .Vhc() call site at once, instead of editing hundreds of individual awaits.

Methods

VhBlock(ValueTask)

Blocks the calling thread until a value task completes, and rethrows its exception.

public static void VhBlock(this ValueTask task)

Parameters

task ValueTask

The task, which must not be awaited elsewhere.

Remarks

A task not complete yet is turned into a Task first, as some value task sources do not allow reading the result before completion.

Exceptions

OperationCanceledException

The task was canceled.

Exception

The task's own exception.

Vhc(Task)

VpnHood's ConfigureAwait. Use .Vhc() on every non-UI await in library and background code in place of .ConfigureAwait(false). It routes through the shared DefaultContinueOnCapturedContext switch, so the whole codebase's context-capture behaviour is controlled from a single place rather than baked into each call site.

public static ConfiguredTaskAwaitable Vhc(this Task task)

Parameters

task Task

The task to await.

Returns

ConfiguredTaskAwaitable

The configured awaitable.

Remarks

With the default switch (false) this is identical to .ConfigureAwait(false) — the correct choice for code that has no affinity to a UI/synchronization context, which is essentially all the core, tunneling, QUIC, TCP-stack and VPN-service code.

Do not use .Vhc() when an await must have a fixed, non-toggleable behaviour:

  • Code that must not capture the context (so it can never be flipped back on) should hardcode .ConfigureAwait(false).
  • Code that must resume on the captured (e.g. UI) context should use a bare await or .ConfigureAwait(true).

Overloads cover Task, Task<TResult>, ValueTask, ValueTask<TResult> and IAsyncEnumerable<T> (for await foreach).

Vhc(ValueTask)

VpnHood's ConfigureAwait. Use .Vhc() on every non-UI await in library and background code in place of .ConfigureAwait(false). It routes through the shared DefaultContinueOnCapturedContext switch, so the whole codebase's context-capture behaviour is controlled from a single place rather than baked into each call site.

public static ConfiguredValueTaskAwaitable Vhc(this ValueTask task)

Parameters

task ValueTask

The task to await.

Returns

ConfiguredValueTaskAwaitable

The configured awaitable.

Remarks

With the default switch (false) this is identical to .ConfigureAwait(false) — the correct choice for code that has no affinity to a UI/synchronization context, which is essentially all the core, tunneling, QUIC, TCP-stack and VPN-service code.

Do not use .Vhc() when an await must have a fixed, non-toggleable behaviour:

  • Code that must not capture the context (so it can never be flipped back on) should hardcode .ConfigureAwait(false).
  • Code that must resume on the captured (e.g. UI) context should use a bare await or .ConfigureAwait(true).

Overloads cover Task, Task<TResult>, ValueTask, ValueTask<TResult> and IAsyncEnumerable<T> (for await foreach).

Vhc<T>(IAsyncEnumerable<T>)

VpnHood's ConfigureAwait. Use .Vhc() on every non-UI await in library and background code in place of .ConfigureAwait(false). It routes through the shared DefaultContinueOnCapturedContext switch, so the whole codebase's context-capture behaviour is controlled from a single place rather than baked into each call site.

public static ConfiguredCancelableAsyncEnumerable<T> Vhc<T>(this IAsyncEnumerable<T> source)

Parameters

source IAsyncEnumerable<T>

The sequence to enumerate.

Returns

ConfiguredCancelableAsyncEnumerable<T>

The configured awaitable.

Type Parameters

T

The type of the items.

Remarks

With the default switch (false) this is identical to .ConfigureAwait(false) — the correct choice for code that has no affinity to a UI/synchronization context, which is essentially all the core, tunneling, QUIC, TCP-stack and VPN-service code.

Do not use .Vhc() when an await must have a fixed, non-toggleable behaviour:

  • Code that must not capture the context (so it can never be flipped back on) should hardcode .ConfigureAwait(false).
  • Code that must resume on the captured (e.g. UI) context should use a bare await or .ConfigureAwait(true).

Overloads cover Task, Task<TResult>, ValueTask, ValueTask<TResult> and IAsyncEnumerable<T> (for await foreach).

Vhc<T>(Task<T>)

VpnHood's ConfigureAwait. Use .Vhc() on every non-UI await in library and background code in place of .ConfigureAwait(false). It routes through the shared DefaultContinueOnCapturedContext switch, so the whole codebase's context-capture behaviour is controlled from a single place rather than baked into each call site.

public static ConfiguredTaskAwaitable<T> Vhc<T>(this Task<T> task)

Parameters

task Task<T>

The task to await.

Returns

ConfiguredTaskAwaitable<T>

The configured awaitable.

Type Parameters

T

The type of the task's result.

Remarks

With the default switch (false) this is identical to .ConfigureAwait(false) — the correct choice for code that has no affinity to a UI/synchronization context, which is essentially all the core, tunneling, QUIC, TCP-stack and VPN-service code.

Do not use .Vhc() when an await must have a fixed, non-toggleable behaviour:

  • Code that must not capture the context (so it can never be flipped back on) should hardcode .ConfigureAwait(false).
  • Code that must resume on the captured (e.g. UI) context should use a bare await or .ConfigureAwait(true).

Overloads cover Task, Task<TResult>, ValueTask, ValueTask<TResult> and IAsyncEnumerable<T> (for await foreach).

Vhc<T>(ValueTask<T>)

VpnHood's ConfigureAwait. Use .Vhc() on every non-UI await in library and background code in place of .ConfigureAwait(false). It routes through the shared DefaultContinueOnCapturedContext switch, so the whole codebase's context-capture behaviour is controlled from a single place rather than baked into each call site.

public static ConfiguredValueTaskAwaitable<T> Vhc<T>(this ValueTask<T> task)

Parameters

task ValueTask<T>

The task to await.

Returns

ConfiguredValueTaskAwaitable<T>

The configured awaitable.

Type Parameters

T

The type of the task's result.

Remarks

With the default switch (false) this is identical to .ConfigureAwait(false) — the correct choice for code that has no affinity to a UI/synchronization context, which is essentially all the core, tunneling, QUIC, TCP-stack and VPN-service code.

Do not use .Vhc() when an await must have a fixed, non-toggleable behaviour:

  • Code that must not capture the context (so it can never be flipped back on) should hardcode .ConfigureAwait(false).
  • Code that must resume on the captured (e.g. UI) context should use a bare await or .ConfigureAwait(true).

Overloads cover Task, Task<TResult>, ValueTask, ValueTask<TResult> and IAsyncEnumerable<T> (for await foreach).