Reputation: 318
I'm currently running into a race condition due to waiting for 0.25 seconds in an async function. A cancellation token is checked in the loop, but happens late due to the wait.
I'm using a library that provides 'Await.Until' functionality, which allows for awaiting boolean values. As such I considered waiting for either 0.25 seconds, or token.isCancelled to be true.
I'm sure outsourcing them to separate tasks, and then using Task.waitAny() would work, but it seems a little over the top. Is there a more elegant way to await multiple awaitables?
while (!Completed)
{
Completed = AdjustPath();
await Await.Seconds(PathingDelay);
if (!loop)
return Completed;
else if (token.IsCancellationRequested)
{
StopMove();
return false;
}
}
The issue above is that the containing function is called again, which sets a destination, before the cancellation is resolved, which stops the object and removes the destination. While I could await the task finishing, that would add a delay to the method running, which is not ideal.
Upvotes: 0
Views: 239
Reputation: 19106
With a TaskCompletionSource<T>
you can build a Task
that will complete when a cancellation is requested by a given CancellationToken
.
public static class Async
{
private static readonly TaskCompletionSource<bool> _neverComplete = new TaskCompletionSource<bool>();
public static Task CompleteOnCancellation( CancellationToken token )
{
if ( !token.CanBeCanceled )
return _neverComplete.Task;
if ( token.IsCancellationRequested )
return Task.CompletedTask;
var tcs = new TaskCompletionSource<bool>();
token.Register( () => tcs.SetResult( true ) );
return tcs.Task;
}
}
Now you can use Task.WhenAny
to interrupt the waiting delay
using var cts = new CancellationTokenSource( 100 );
var longWaitingTask = Task.Delay( 250 );
var cancellationTask = Async.CompleteOnCancellation( cts.Token );
var completedTask = await Task.WhenAny( longWaitingTask, cancellationTask );
if ( completedTask == longWaitingTask )
{
Console.WriteLine( "Long Running Task completed." );
}
if ( completedTask == cancellationTask )
{
Console.WriteLine( "Cancellation Task completed." );
}
Upvotes: 1
Reputation: 647
Use Delay(TimeSpan, CancellationToken), like Dmitry already proposed in his comment.
while (!Completed)
{
Completed = AdjustPath();
try
{
await Task.Delay(PathingDelay, token);
}
catch(TaskCanceledException e){} // if delay was cancelled is checked in next lines
if (!loop)
return Completed;
else if (token.IsCancellationRequested)
{
StopMove();
return false;
}
}
Upvotes: 0