What is the best way to complete a task to prevent escape
I've created a function below that will wait for all tasks to complete or throw an exception when canceled or timed out.
public static async Task WhenAll(
IEnumerable<Task> tasks,
CancellationToken cancellationToken,
int millisecondsTimeOut)
{
Task timeoutTask = Task.Delay(millisecondsTimeOut, cancellationToken);
Task completedTask = await Task.WhenAny(
Task.WhenAll(tasks),
timeoutTask
);
if (completedTask == timeoutTask)
{
throw new TimeoutException();
}
}
If everything is tasks
finished before a long timeout (i.e. millisecondsTimeOut
= 60,000), will there timeoutTask
remain about 60 seconds even after the function returns? If so, what is the best way to fix the flight problem?
source to share
Yes, it timeoutTask
will hang until this timeout expires (or canceled CancellationToken
).
You can fix this by passing another CancellationToken
one you get from the new CancellationTokenSource
one you create using CancellationTokenSource.CreateLinkedTokenSource
and canceling at the end. You must also wait for the task to complete, otherwise you won't see an exception (or cancellation):
public static async Task WhenAll(
IEnumerable<Task> tasks,
CancellationToken cancellationToken,
int millisecondsTimeOut)
{
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var timeoutTask = Task.Delay(millisecondsTimeOut, cancellationTokenSource.Token);
var completedTask = await Task.WhenAny(Task.WhenAll(tasks), timeoutTask);
if (completedTask == timeoutTask)
{
throw new TimeoutException();
}
cancellationTokenSource.Cancel();
await completedTask;
}
However, I think there is an easier way to achieve what you want, if you don't need to differentiate between TimeoutException
and TaskCancelledException
. You just add a continuation that is canceled when CancellationToken
canceled or when the timeout is complete:
public static Task WhenAll(
IEnumerable<Task> tasks,
CancellationToken cancellationToken,
int millisecondsTimeOut)
{
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cancellationTokenSource.CancelAfter(millisecondsTimeOut);
return Task.WhenAll(tasks).ContinueWith(
_ => _.GetAwaiter().GetResult(),
cancellationTokenSource.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
source to share