Class Transport<T>
- Namespace
- MarymoorStudios.Core.Promises
- Assembly
- MarymoorStudios.Core.Promises.dll
A FIFO queue of ordered discrete items.
public abstract class Transport<T> : IAsyncDisposable
Type Parameters
T
The item type.
- Inheritance
-
Transport<T>
- Implements
- Derived
- Inherited Members
Remarks
The Transport<T> is a FIFO for communicating a totally ordered series of "items" from a producer to a consumer. Though the Transport<T> is thread-safe, it supports only a single producer and a single consumer at one time. The producer and consumer MAY (and usually do) operate in separate threads. (The producer and consumer MAY operate in a single thread, but care should be taken when waiting on the handles (see below) to ensure the thread is not live-locked.)
A Transport<T> takes "ownership" of any item written to the queue and subsequently transfers "ownership" to the consumer when the item is read. The consumer is responsible for releasing or disposing any linear resources transferred through the queue (including memory buffers).
The Transport<T> supports efficient blocking for both producer and consumer operations through the ProducerEvent and ConsumerEvent properties respectively. The producer handle is guaranteed to be signaled ONLY AFTER a failed enqueue operation due to the queue being full. Similarly, the consumer handle is guaranteed to be signaled ONLY AFTER a failed dequeue operation due to the queue being empty. Both the producer and the consumer should loop performing their operations until a blocking condition is so indicated before attempting to wait on their respective handle. The wait handles MAY over-signal (i.e. signal even though the corresponding operation will fail due to resource constraints). The caller should be prepared to wait again if that happens.
All events will be signaled at least once if the buffer is "closed" by either party. Events are NOT guaranteed to be signaled MORE THAN ONCE after a Close(TransportFlags), so both parties MUST aggressively respond to closure indications. It is "safe" to call any operation after a successful "close" by either (or both) parties. Producers will receive a persistent AbortedException on all enqueue attempts after closure. Consumers will receive a persistent AbortedException on all dequeue attempts ONLY AFTER all pending items have been delivered (allowing the consumer to release and dispose all pending resources safely). Both parties MUST call Close(TransportFlags) at least once.
In general the consumer "owns" the Transport<T> and is responsible for calling
DisposeAsync() when it is no longer needed. The consumer MUST ensure that the producer has
released any references to the ProducerEvent which it does so by calling Close(TransportFlags) on
its side and receiving a true
response. The producer should NEVER access any part of the
Transport<T> or its handles AFTER calling Close(TransportFlags) for its side. The consumer MUST
release any references to the ConsumerEvent BEFORE calling DisposeAsync(). If the
consumer initiates closure it should:
- Call TryDequeue(out T) in a loop until the queue is empty, then
-
Call Close(TransportFlags) again. If Close(TransportFlags) returns
true
then it is "safe" to dispose, otherwise. - Wait on ConsumerEvent until it signals (indicating that the producer has called Close(TransportFlags)) and then dispose.
It is "safe" for the producer to dispose the Transport<T> ONLY IF the consumer was never successfully started.
Constructors
Transport()
protected Transport()
Properties
ConsumerEvent
Event to efficiently signal the consumer when items are available to read.
public abstract WaitHandle ConsumerEvent { get; }
Property Value
Remarks
This event only signals on the transition between an empty queue and having at least one item, so the caller should read the queue to completion before waiting on this event.
ProducerEvent
Event to efficiently signal the producer when slots are available to write.
public abstract WaitHandle ProducerEvent { get; }
Property Value
Remarks
This event only signals on the transition between a full queue and having at least threshold
items, so the caller should write to the queue until full before waiting on this event.
Methods
Close(TransportFlags)
Marks the buffer as closed atomically.
public abstract bool Close(TransportFlags flags = TransportFlags.Closed)
Parameters
flags
TransportFlagsIndicates which side(s) to close.
Returns
- bool
True if the both sides have closed, false if only one side has closed as of this call.
Remarks
No more items can be inserted after this call returns.
Either side (producer or consumer) can close the buffer.
Typically, the consumer will continue to read items until TryDequeue(out T) indicates the closure has been seen. This ensures that all buffered items have been removed, regardless of which side closed first.
Both sides should close the buffer before the last side to close can then safely dispose.
DisposeAsync()
public ValueTask DisposeAsync()
Returns
DisposeAsync(bool)
Standard dispose pattern for subclasses.
protected virtual ValueTask DisposeAsync(bool disposing)
Parameters
disposing
boolTrue if both native and managed resources are being disposed. False if only native resources (i.e. in finalizer). When native only then MUST be prompt.
Returns
~Transport()
Finalizer.
protected ~Transport()
TryDequeue(out T)
Attempt to remove an item from the front of the buffer.
public abstract bool TryDequeue(out T item)
Parameters
item
TIf successful, the item removed from the buffer,
default
otherwise.
Returns
- bool
True if an item was successfully remove, false if the buffer is empty.
Remarks
If item
is successfully removed the caller takes ownership of it and is
responsible for releasing its resources. If the attempt to dequeue fails then item
is
default
.
Exceptions
- AbortedException
If the buffer has been closed AND all buffered items have been read.
TryEnqueue(ref T)
Attempt to insert an item at the end of the buffer.
public abstract bool TryEnqueue(ref T item)
Parameters
item
TThe item to be inserted.
Returns
- bool
True if the item was inserted, false if the buffer is full.
Remarks
On a successful insert the buffer takes ownership of item
and its value is set to
default
. If item
cannot be inserted (either because 'full' or 'closed') then false
is returned and item
is unmodified (still owned by the caller); the caller is then
responsible for releasing any resources.
Exceptions
- AbortedException
If the buffer has already been closed.