# Cancelling A Task Without Cancellation Token

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 (cancellationToken.IsCancellationRequested)
{
}
}

}


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)
{
{
try
{

//...

}
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]
{
var stopWatch = new Stopwatch();
stopWatch.Start();

var completed = false;

{
//Iterate for one second
for (var i = 0; i < 100; i++)
{
}

return true;
});

var cancellationTokenSource = new CancellationTokenSource();

//Start a task that will cancel in 500 milliseconds
{
cancellationTokenSource.Cancel();
return true;
});

//Get a task that will finish when the cancellation token is cancelled

//Wait for the first task to finish
{
});

//Ensure the task didn't wait a long time
Assert.IsTrue(stopWatch.ElapsedMilliseconds < 1000);

Assert.IsFalse(completed);
}


Starting with the first function, I would make a few changes:

public static async Task<T> SynchronizeWithCancellationToken<T>(this Task<T> task, int delayMilliseconds = 10, CancellationToken cancellationToken = default)
{

{
cancellationToken.ThrowIfCancellationRequested();
}

}


This is about as good as it gets to "add cancellation" to a Task that doesn't do cancellation. The key to understanding Task cancellation in .NET is that it is cooperative. This means that simply passing a cancellation token around doesn't do anything on its own; instead the Task implementation itself has to handle cancellation. This makes a lot of sense since only the Task implementation itself knows when best to allow cancellation without causing invalid state or corruption.

I would definitely recommend taking the time to do a read-through of the MSDN article on this: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation?view=netframework-4.8

Your second function attempts to use the Task.Run overload that takes a CancellationToken in order to cancel the operation. However, Task.Run only uses the token to avoid starting the Task (if it's already cancelled when it goes to run it), and it's left to the user to use the token inside the implementation passed to Task.Run to handle further cancellation. See here: https://stackoverflow.com/questions/22637642/using-cancellationtoken-for-timeout-in-task-run-does-not-work

The first method is definitely the one you want to use. Just remember that it isn't true cancellation which would require you to modify the implementation of the API function and add the cooperative cancellation bits. When you add cancellation like this, without the implementation bits, you're only cancelling the wait for the Task to finish, not the Task itself. If the Task has further operations to carry out, they will still be carried out. This can often times be a deal breaker with some APIs, and you might be forced to find an alternative implementation that handles cancellation, or even implement your own.

Edit:

It also slipped my mind that you can use CancellationToken.Register to asynchronously wait and get rid of the polling...

    public static async Task ToTask(this CancellationToken token)
{
using (token.Register(() => source.SetResult(null)))
}