I'd like to add a timeout to ChannelReader.ReadyAsync. Here are two solutions I've found:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
try {
var data = chan.ReadAsync(cts.Token);
} catch (OperationCanceledException) {
// timeout
}
var tasks = new Task[] { Task.Delay(2000), chan.ReadAsync(CancellationToken.None) };
var completedTask = await Task.WhenAny(tasks);
if (completedTask == tasks[0])
// timeout
else
var data = ((T)completedTask).Result;
However, both these solutions aren't allocation-free. The first allocates a CancellationTokenSource and the second one a Timer in the Task.Delay. Is there a way to make a similar code without any allocation?
Thanks for your answers, they made me think again of exactly I was looking for: reuse CancellationTokenSource
. Once a CancellationTokenSource
is cancelled, you can't reuse it. But in my case, ChannelReader.ReadAsync
would most of the time return before the timeout triggers so I've used the fact that CancelAfter
doesn't recreate a timer the second time you call it to avoid cancelling the CancellationTokenSource
after ChannelReader.ReadAsync
returns.
var timeoutCancellation = new CancellationTokenSource();
while (true)
{
if (timeoutCancellation.IsCancellationRequested)
{
timeoutCancellation.Dispose();
timeoutCancellation = new CancellationTokenSource();
}
T data;
try
{
timeoutCancellation.CancelAfter(2000);
data = await _queue.Reader.ReadAsync(timeoutCancellation.Token);
// make sure it doesn't get cancelled so it can be reused in the next iteration
// Timeout.Infinite won't work because it would delete the underlying timer
timeoutCancellation.CancelAfter(int.MaxValue);
}
catch (OperationCanceledException) // timeout reached
{
// handle timeout
continue;
}
// process data
}
This is not allocation-free but it reduces drastically the number of allocated objects.
EDIT 1: In .NET 6 you can also use CancellationTokenSource.TryReset to reuse a CancellationTokenSource.