In this case, I would only use undo tokens to undo. A repeatable timeout, such as a save timer, is better represented as a timer.
So, I would simulate this as three excellent tasks. First, the cancellation token:
All messages were canceled by the network layer.
CancellationToken token = ...;
Then three simultaneous operations:
Byte received
var readByteTask = stream.ReadAsync(buf, 0, 1, token);
The standby timer has expired
var keepAliveTimerTask = Task.Delay(TimeSpan.FromSeconds(10), token);
Message from network stream available
This is a little trickier. Your current code uses a BlockingCollection<T> , which is not compatible with asynchronous. I recommend switching to TPL Dataflow BufferBlock<T> or my own AsyncProducerConsumerQueue<T> , any of which can be used as async-compatible producer / consumer queues (which means that the producer can synchronize either asynchronously and the consumer can synchronize or asynchronously).
BufferBlock<byte[]> SendQueue = new ...; ... var messageTask = SendQueue.ReceiveAsync(token);
Then you can use Task.WhenAny to determine which of these tasks has been completed:
var completedTask = await Task.WhenAny(readByteTask, keepAliveTimerTask, messageTask);
Now you can get the results by comparing completedTask with others and await with them:
if (completedTask == readByteTask) { // Throw an exception if there was a read error or cancellation. await readByteTask; var byte = buf[0]; ... // Continue reading readByteTask = stream.ReadAsync(buf, 0, 1, token); } else if (completedTask == keepAliveTimerTask) { // Throw an exception if there was a cancellation. await keepAliveTimerTask; ... // Restart keepalive timer. keepAliveTimerTask = Task.Delay(TimeSpan.FromSeconds(10), token); } else if (completedTask == messageTask) { // Throw an exception if there was a cancellation (or the SendQueue was marked as completed) byte[] message = await messageTask; ... // Continue reading messageTask = SendQueue.ReceiveAsync(token); }