Cancelling A Task Without Cancellation Token
Starting with the first function, I would make a few changes:


Task.IsCompleted covers all completion states already (https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.iscompleted?view=netframework-4.8)
Tasks are all about not blocking the current thread. Never use Thread.Sleep and always use Task.Delay.
Cancellation can be more succinctly handled with Task.ThrowIfCancellationRequested


public static async Task&lt;T&gt; SynchronizeWithCancellationToken&lt;T&gt;(this Task&lt;T&gt; task, int delayMilliseconds = 10, CancellationToken cancellationToken = default)
{
    if (task == null)
        throw new ArgumentNullException(nameof(task));

    while (!task.IsCompleted)
    {
        await Task.Delay(delayMilliseconds);
        cancellationToken.ThrowIfCancellationRequested();
    }

    return await task;
}


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)
    {
        var source = new TaskCompletionSource&lt;object&gt;();
        using (token.Register(() =&gt; source.SetResult(null)))
            await source.Task;
    }

    public static async Task&lt;T&gt; SynchronizeWithCancellationToken&lt;T&gt;(this Task&lt;T&gt; task,
        CancellationToken cancellationToken = default, string message = default)
    {
        if (task == null)
            throw new ArgumentNullException(nameof(task));

        if (await Task.WhenAny(task, cancellationToken.ToTask()) == task)
            return await task;
        else
            throw new OperationCanceledException(message ?? "The operation was canceled.");
    }