12

How can I pause the executing until the cancellation is requested?

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    // Wait for the Cancel...

    Console.WriteLine("Canceled!");
});

Console.ReadKey();

cts.Cancel();

Console.ReadKey();
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
user4388177
  • 2,433
  • 3
  • 16
  • 30

4 Answers4

23

You can use the WaitHandle to wait synchronously:

static void Main()
{
    var cts = new CancellationTokenSource();

    Task.Run(() =>
    {
        // Wait for the Cancel...

        cts.Token.WaitHandle.WaitOne();

        Console.WriteLine("Canceled!");
    });

    Console.ReadKey();

    cts.Cancel();

    Console.ReadKey();
}

That said, "blocking a thread until something is cancelled" is a very uncommon scenario, so it's possible that you're using the wrong tool for this job. If you need to wait on something (not specifically a cancellation), you can use a TaskCompletionSource instead. If you need to react to a cancellation, you can use CancellationToken.Register to attach a callback (and therefore avoid blocking a thread).

Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94
  • In the link that @uno provided, it uses WaitAny instead of WaitOne on the handle, do you know why are they using a different approach? – user4388177 Oct 09 '16 at 11:14
  • @user4388177 In the link, they use `WaitHandle.WaitAny` when they need to wait on multiple handles. If you have only one, then `WaitOne` does the trick – Kevin Gosse Oct 09 '16 at 11:34
  • They are actually waiting just one event wrapped into an array, sounds odd to me, but being from the Microsoft pfxteam blog makes me thing it might be somehow the right thing to do. – user4388177 Oct 09 '16 at 11:42
  • 2
    @user4388177 Are we reading the same article? I see `WaitHandle.WaitAny(new [] {wh, token.WaitHandle}); ` That's two handles – Kevin Gosse Oct 09 '16 at 11:44
  • `TaskCompletionSource` is the way to go. Since you want to 'cancel' or stop the execution. You are basically completing the operation. – hwcverwe Dec 28 '22 at 10:05
13

The CancellationTokenSource uses a ManualResetEvent internally and you can just wait for the exposed WaitHandle to pause the execution until it is set.

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    WaitHandle.WaitAny(new[] { cts.Token.WaitHandle });

    Console.WriteLine("Canceled!");
});

Console.ReadKey();

cts.Cancel();

Console.ReadKey();

This is the WaitHandle defined in the CancellationTokenSource:

ManualResetEvent mre = new ManualResetEvent(false);
if (Interlocked.CompareExchange(ref m_kernelEvent, mre, null) != null)
{    
    ((IDisposable)mre).Dispose();
}

// There is a ---- between checking IsCancellationRequested and setting the event.
// However, at this point, the kernel object definitely exists and the cases are:
//   1. if IsCancellationRequested = true, then we will call Set()
//   2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set().
if (IsCancellationRequested)
    m_kernelEvent.Set();

return m_kernelEvent;

And the Token just returns the handle from the source (has an internal variable referencing it).

Another option is to register the Token callback and use your own ManualResetEvent:

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    var mre = new ManualResetEvent(false);

    var registration = cts.Token.Register(() => mre.Set());

    using (registration)
    {
        mre.WaitOne();

        Console.WriteLine("Canceled!");
    }
});

Console.ReadKey();

cts.Cancel();

Console.ReadKey();

Examples: https://blogs.msdn.microsoft.com/pfxteam/2009/05/22/net-4-cancellation-framework/

Stefano d'Antonio
  • 5,874
  • 3
  • 32
  • 45
  • In the link provided, it uses WaitAny instead of WaitOne on the handle as @KooKiz suggested, do you know why are they using a different approach? – user4388177 Oct 09 '16 at 11:15
  • 2
    In the link there are both WaitOne/WaitAny. It does not matter if you use WaitOne or WaitAny on an array with one WaitHandle. It is the same. – Sir Rufo Oct 09 '16 at 11:25
  • 2
    @user4388177 in the link is waiting on two handles, here you can use `WaitOne`, I just copied the example from the post and modified it without "refactoring". – Stefano d'Antonio Oct 09 '16 at 12:01
5

The most concise way is probably this:

try { await Task.Delay(Timeout.Infinite, cts.Token); } catch { }

This approach relies on catching an exception, and exceptions are expensive. This should not be a problem if you are awaiting tokens sporadically, but in case you are awaiting tokens in tight loops, you may want to include in your project a cheap awaiter for CancellationTokens, as described in this article. It will allow you to do this:

await cts.Token;

Alternative: The exception can be suppressed also with this trick:

await Task.WhenAny(Task.Delay(Timeout.Infinite, cts.Token));

Using the Task.WhenAny has some implications regarding the TaskScheduler.UnobservedTaskException event, as explained in this answer.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    the comment about the UnobservedTaskException is valid in general, but the link mentions it does not triggered for the *canceled* task. – eglasius Jun 28 '23 at 11:35
1

This code can be used to wait for a cancellation event without blocking the thread.

.NET6 version can be really simple and allows await to be cancellable

public async Task Run(CancellationToken cancellationToken)
{
    // Simplification for the sake of example
    var cts = new CancellationTokenSource();
    
    var waitForStop = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
    // IHostApplicationLifetime event can be used instead of `cts.Token`
    CancellationTokenRegistration registration = cts.Token.Register(() => waitForStop.SetResult());
    await using var _ = registration.ConfigureAwait(false);
    
    await waitForStop.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
}

The TaskCanceledException won't be thrown because of the cts.Cancel() call. It will allow to do something (e.g. graceful shutdown) before leaving the method. However if cancellation requested for cancellationToken it will throw the TaskCanceledException as expected.

In case the developer needs to return the result:

var cts = new CancellationTokenSource();

var waitForStop = new TaskCompletionSource<bool?>(TaskCreationOptions.RunContinuationsAsynchronously);
cts.Token.Register(obj =>
{
    var tcs = (TaskCompletionSource<bool?>)obj!;
    tcs.TrySetResult(true);
}, waitForStop);

var result = await waitForStop.Task.ConfigureAwait(false);
Pavel
  • 1,015
  • 3
  • 13
  • 27