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
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
taskValueTaskThe 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
taskTaskThe 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
awaitor.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
taskValueTaskThe 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
awaitor.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
sourceIAsyncEnumerable<T>The sequence to enumerate.
Returns
- ConfiguredCancelableAsyncEnumerable<T>
The configured awaitable.
Type Parameters
TThe 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
awaitor.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
taskTask<T>The task to await.
Returns
- ConfiguredTaskAwaitable<T>
The configured awaitable.
Type Parameters
TThe 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
awaitor.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
taskValueTask<T>The task to await.
Returns
- ConfiguredValueTaskAwaitable<T>
The configured awaitable.
Type Parameters
TThe 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
awaitor.ConfigureAwait(true).
Overloads cover Task, Task<TResult>, ValueTask,
ValueTask<TResult> and IAsyncEnumerable<T> (for await foreach).