Some Task methods don't take cancellation tokens. This is a problem because some long-running tasks may never finish and there is no way to send them a cancellation request. This seems a bit dodgy to me but this is a workaround. Is there anything wrong with this? Is there a better way?
public static Task<T> SynchronizeWithCancellationToken<T>(this Task<T> task, int delayMilliseconds = 10, CancellationToken cancellationToken = default)
{
if (task == null) throw new ArgumentNullException(nameof(task));
while (!task.IsCompleted && !task.IsFaulted && !task.IsCanceled)
{
Thread.Sleep(delayMilliseconds);
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<T>(cancellationToken);
}
}
return task;
}
Example
The methods where this is a problem tend to be obscure APIs that haven't had much love in a long time or APIs that are wrappers for other platforms. One example is RequestWaitAsync
on UsbDeviceConnection
. It's a wrapper for an Android USB API . The issue can be worked around like so:
var buffers = await GetReadResultAsync(bufferLength, byteBuffer).SynchronizeWithCancellationToken(cancellationToken);
Another way to workaround the issue might be with Task.Run
public async Task<ReadResult> ReadAsync(uint bufferLength, CancellationToken cancellationToken = default)
{
return await Task.Run(async () =>
{
try
{
//...
return await GetReadResultAsync(bufferLength, byteBuffer);
}
catch (Exception ex)
{
//...
}
}, cancellationToken);
}
Which of these two work arounds are the safest?
Unit Test
This is a pathetic attempt at a unit test. It passes:
[TestMethod]
public async Task TestSynchronizeWithCancellationToken()
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var completed = false;
var task = Task.Run<bool>(() =>
{
//Iterate for one second
for (var i = 0; i < 100; i++)
{
Thread.Sleep(10);
}
return true;
});
var cancellationTokenSource = new CancellationTokenSource();
//Start a task that will cancel in 500 milliseconds
var cancelTask = Task.Run<bool>(() =>
{
Thread.Sleep(500);
cancellationTokenSource.Cancel();
return true;
});
//Get a task that will finish when the cancellation token is cancelled
var syncTask = task.SynchronizeWithCancellationToken(cancellationToken: cancellationTokenSource.Token);
//Wait for the first task to finish
var completedTask = (Task<bool>) await Task.WhenAny(new Task[]
{
syncTask,
cancelTask
});
//Ensure the task didn't wait a long time
Assert.IsTrue(stopWatch.ElapsedMilliseconds < 1000);
//Ensure the task wasn't completed
Assert.IsFalse(completed);
}