Table of Contents

Class AsyncExchange<T>

Namespace
CryptoHives.Foundation.Threading.Async.Pooled
Assembly
CryptoHives.Foundation.Threading.dll

A two-party rendezvous that lets two tasks atomically swap values without allocations, equivalent to Java's Exchanger<V> for async code.

public sealed class AsyncExchange<T> : IResettable

Type Parameters

T

The type of value exchanged.

Inheritance
AsyncExchange<T>
Implements
Inherited Members

Remarks

Each caller supplies a value and receives the counterpart's value. The first caller to arrive suspends until a second caller arrives; the second caller completes the first immediately and both return synchronously with each other's value.

Exactly one "slot" is maintained at a time. If multiple callers arrive while a waiter is already pending, each new caller pairs with and wakes the pending waiter; the next caller then becomes the new pending waiter.

private readonly AsyncExchange<int> _exchange = new();

// Task A int fromB = await _exchange.ExchangeAsync(42, ct);

// Task B (concurrent with A) int fromA = await _exchange.ExchangeAsync(99, ct); // fromA == 42, fromB == 99

Optional timeout and cancellation token parameters on ExchangeAsync(T, TimeSpan, CancellationToken). A timeout of Zero turns the call into a try-exchange: it pairs with a counterpart that is already waiting and throws TimeoutException otherwise, without ever occupying the slot. TryExchange(T, out T) answers the same question synchronously, without the exception or the ValueTask<TResult> allocation a failed zero-timeout attempt would otherwise need.

This implementation uses ValueTask<TResult> for waiters and provides allocation-free exchange by reusing a per-instance LocalManualResetValueTaskSource<T> for the common single-waiter case and falling back to a pooled IGetPooledManualResetValueTaskSource<T> for concurrent contention.

Important Usage Note: awaiting a ValueTask<TResult> has its own caveats, as it is a struct that can only be awaited or converted with AsTask() ONE single time. Additional attempts to await, to convert, or even to read IsCompleted after the first await throw an InvalidOperationException.

The IResettable interface is implemented to allow resetting the state of the instance for reuse by an ObjectPool<T> using the DefaultObjectPool<T> implementation.

Constructors

AsyncExchange(bool, IGetPooledManualResetValueTaskSource<T>?)

Constructs a new AsyncExchange<T> instance.

public AsyncExchange(bool runContinuationAsynchronously = true, IGetPooledManualResetValueTaskSource<T>? pool = null)

Parameters

runContinuationAsynchronously bool

When true (default), the waiting party's continuation is forced to the thread pool when the exchange completes, preventing the arriving party's thread from being hijacked.

pool IGetPooledManualResetValueTaskSource<T>

Custom pool for waiter instances; uses a shared per-T pool if omitted.

Properties

HasWaiter

Gets whether a task is currently waiting for an exchange partner.

public bool HasWaiter { get; }

Property Value

bool

RunContinuationAsynchronously

Gets or sets whether the waiting party's continuation is forced to run asynchronously when the exchange is completed by the arriving party.

public bool RunContinuationAsynchronously { get; set; }

Property Value

bool

Methods

ExchangeAsync(T, CancellationToken)

Exchanges value with a counterpart task and returns that task's value. If no counterpart is waiting, suspends until one arrives or cancellationToken fires. If a counterpart is already waiting, completes both tasks immediately without suspending.

public ValueTask<T> ExchangeAsync(T value, CancellationToken cancellationToken = default)

Parameters

value T

The value to offer to the counterpart.

cancellationToken CancellationToken

Token to cancel the wait.

Returns

ValueTask<T>

A ValueTask<TResult> that completes with the counterpart's offered value.

Exceptions

OperationCanceledException

Thrown when cancellationToken is cancelled while waiting.

ExchangeAsync(T, TimeSpan, CancellationToken)

Exchanges value with a counterpart task and returns that task's value, or fails once timeout has elapsed.

public ValueTask<T> ExchangeAsync(T value, TimeSpan timeout, CancellationToken cancellationToken = default)

Parameters

value T

The value to offer to the counterpart.

timeout TimeSpan

The maximum time to wait for a counterpart. Use InfiniteTimeSpan to wait indefinitely, or Zero to pair only with a counterpart that is already waiting - TryExchange(T, out T) does the same synchronously, without throwing on a miss.

cancellationToken CancellationToken

Token to cancel the wait.

Returns

ValueTask<T>

A ValueTask<TResult> that completes with the counterpart's offered value.

Remarks

If a counterpart is already waiting the exchange completes immediately without allocating any timeout infrastructure. A timer is allocated only when this caller has to wait and a finite positive timeout is requested; it is disposed automatically when the returned ValueTask<TResult> is awaited.

Exceptions

ArgumentOutOfRangeException

Thrown when timeout is negative and not equal to InfiniteTimeSpan.

TimeoutException

Thrown when the timeout elapses before a counterpart arrives.

OperationCanceledException

Thrown when cancellationToken is cancelled while waiting.

TryExchange(T, out T)

Attempts to exchange value immediately, without waiting.

public bool TryExchange(T value, out T result)

Parameters

value T

The value to offer to the counterpart.

result T

The counterpart's value, if this method returns true. Undefined if this method returns false.

Returns

bool

true if a counterpart was already waiting and the exchange completed; false if nobody was waiting.

Remarks

Synchronous and non-throwing by design: unlike ExchangeAsync(T, TimeSpan, CancellationToken) with a zero timeout, a failed attempt here never allocates an exception or a faulted ValueTask<TResult> - there is nothing to await in the first place, since this either pairs immediately or doesn't. A failed attempt never occupies the slot, exactly like the zero-timeout try-exchange.

TryReset()

Reset the object to a neutral state, semantically similar to when the object was first constructed.

public bool TryReset()

Returns

bool

true if the object was able to reset itself, otherwise false.

Remarks

In general, this method is not expected to be thread-safe.