166

Compared to the preceding code for class RulyCanceler, I wanted to run code using CancellationTokenSource.

How do I use it as mentioned in Cancellation Tokens, i.e. without throwing/catching an exception? Can I use the IsCancellationRequested property?

I attempted to use it like this:

cancelToken.ThrowIfCancellationRequested();

and

try
{
  new Thread(() => Work(cancelSource.Token)).Start();
}
catch (OperationCanceledException)
{
  Console.WriteLine("Canceled!");
}

but this gave a run-time error on cancelToken.ThrowIfCancellationRequested(); in method Work(CancellationToken cancelToken):

System.OperationCanceledException was unhandled
  Message=The operation was canceled.
  Source=mscorlib
  StackTrace:
       at System.Threading.CancellationToken.ThrowIfCancellationRequested()
       at _7CancellationTokens.Token.Work(CancellationToken cancelToken) in C:\xxx\Token.cs:line 33
       at _7CancellationTokens.Token.<>c__DisplayClass1.<Main>b__0() in C:\xxx\Token.cs:line 22
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

The code that I successfully ran caught the OperationCanceledException in the new thread:

using System;
using System.Threading;
namespace _7CancellationTokens
{
  internal class Token
  {
    private static void Main()
    {
      var cancelSource = new CancellationTokenSource();
      new Thread(() =>
      {
         try
         {
           Work(cancelSource.Token); //).Start();
         }
         catch (OperationCanceledException)
         {
            Console.WriteLine("Canceled!");
         }
         }).Start();

      Thread.Sleep(1000);
      cancelSource.Cancel(); // Safely cancel worker.
      Console.ReadLine();
    }
    private static void Work(CancellationToken cancelToken)
    {
      while (true)
      {
        Console.Write("345");
        cancelToken.ThrowIfCancellationRequested();
      }
    }
  }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Fulproof
  • 4,466
  • 6
  • 30
  • 50
  • 2
    https://learn.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads has some pretty good examples of using `CancellationTokenSource` with async methods, long running methods with polling, and using a callback. – Ehtesh Choudhury Jun 16 '18 at 19:37
  • [This](https://binary-studio.com/2015/10/23/task-cancellation-in-c-and-things-you-should-know-about-it/) articles shows the options you have and need to handle the token according to your specific case. – Ognyan Dimitrov Oct 09 '19 at 12:54

4 Answers4

191

You can implement your work method as follows:

private static void Work(CancellationToken cancelToken)
{
    while (true)
    {
        if(cancelToken.IsCancellationRequested)
        {
            return;
        }
        Console.Write("345");
    }
}

That's it. You always need to handle cancellation by yourself - exit from method when it is appropriate time to exit (so that your work and data is in consistent state)

UPDATE: I prefer not writing while (!cancelToken.IsCancellationRequested) because often there are few exit points where you can stop executing safely across loop body, and loop usually have some logical condition to exit (iterate over all items in collection etc.). So I believe it's better not to mix that conditions as they have different intention.

Cautionary note about avoiding CancellationToken.ThrowIfCancellationRequested():

Comment in question by Eamon Nerbonne:

... replacing ThrowIfCancellationRequested with a bunch of checks for IsCancellationRequested exits gracefully, as this answer says. But that's not just an implementation detail; that affects observable behavior: the task will no longer end in the cancelled state, but in RanToCompletion. And that can affect not just explicit state checks, but also, more subtly, task chaining with e.g. ContinueWith, depending on the TaskContinuationOptions used. I'd say that avoiding ThrowIfCancellationRequested is dangerous advice.

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Sasha
  • 8,537
  • 4
  • 49
  • 76
  • 1
    Thanks! This does not follow from the online text, quite authoritative (book "C# 4.0 in a Nutshell"?) I cited. Could you give me a reference about "always"? – Fulproof Feb 25 '13 at 13:37
  • 1
    This comes from practice and experience =). I can't remember where from I know this. I used "you always need" because you actually can interrupt the worker thread with exception from outside using Thread.Abort(), but that's a very bad practice. By the way, using CancellationToken.ThrowIfCancellationRequested() is also "handle cancellation by yourself", just the other way to do it. – Sasha Feb 25 '13 at 13:57
  • ^ that's correct. It would just be instead of the `if` block above you would put `cancelToken.ThrowIfCancellationRequested();`. Sometimes the documentation / books are off (even when written by MVPs, etc.) -- it's unfortunate, but maybe it will be fixed in the next edition. In the meanwhile, perhaps you can check if the book has an online errata site (and if the mistake isn't mentioned, perhaps you can submit your findings to the author for future correction). – BrainSlugs83 Feb 10 '14 at 22:54
  • 1
    Can you do while(!cancelToken.IsCancellationRequested)? – Doug Dawson Feb 21 '14 at 21:09
  • @DougDawson, sure you can. That just adds one more level of indention and Resharper recommends not doing this as it reduces code redability. – Sasha Feb 22 '14 at 12:00
  • 2
    @OleksandrPshenychnyy I meant replace while(true) with while(!cancelToken.IsCancellationRequested). This was helpful! Thanks! – Doug Dawson Feb 27 '14 at 16:11
  • 1
    @Fulproof There's no generic way for a runtime to cancel running code because runtimes are not smart enough to know where a process can be interrupted. In some cases it is possible to simply exit a loop, in other cases more complex logic is needed, ie transactions have to be rolled back, resources have to be released (eg file handles or network connections). This is why there no magical way of canceling a task without having to write some code. What you think of is like killing a process but that's not cancel that's one of the worst things can happen to an application because can't clean up. – user3285954 Jun 25 '14 at 14:57
  • while (!cancellationToken.IsCancellationRequested) { DoWork(); } – user3285954 Jun 25 '14 at 15:01
  • @Oleksandr Pshenychnyy Re your update. Your code sample is an endless loop this is why IsCancellationRequested could be used as exit condition. If you want to illustrate exit condition use while (workIsNotCompleted) ... – user3285954 May 29 '15 at 21:17
  • @user3285954, it is just an example. Depending on your needs loop exit condition may be different - it doesn't affect the subject of the thread. By the way, there might be many exit points from the loop - everything depending on concrete problem you solve. – Sasha May 30 '15 at 20:16
  • 1
    Cancellation of async workflows becomes complex quickly, and the "it depends" nature of the commentary is nice, but there is a reason the "default" (or maybe "initial go-to") method for handling cancellation is `ThrowIfCancellationRequested()`: namely, unless the signature or naming comminicate that cancellation is "normal" for the method, cancellation should be considered an abnormal halt. [See here](https://blogs.msdn.microsoft.com/pfxteam/2009/05/22/net-4-cancellation-framework/), especially the initial commentary by Toub and Liddell. This answer should be edited. – Marc L. Mar 21 '18 at 15:01
  • What will happen if I initialize `CancellationToken` parameter in the method as `None`, so no new `CancellationTokenSource` is created? I start method as `PeriodicCheck(TimeSpan.FromSeconds(60), CancellationToken.None)`, and when I stop WPF application, everything stops fine. Should I create `CancellationTokenSource` all the time, or just when I want to manage when process should be stopped? – kosist May 08 '20 at 21:44
  • 3
    @kosist You can use CancellationToken.None if you do not plan to cancel the operation you are starting manually. Of course when system process is killed, everything is interrupted and CancellationToken has nothing to do with that. So yes, you should only create CancellationTokenSource if you really need to use it to cancel the operation. There is no sense creating something you do not use. – Sasha May 09 '20 at 13:46
27

You have to pass the CancellationToken to the Task, which will periodically monitors the token to see whether cancellation is requested.

// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;  

// Task need to be cancelled with CancellationToken 
Task task = Task.Run(async () => {     
  while(!token.IsCancellationRequested) {
      Console.Write("*");         
      await Task.Delay(1000);
  }
}, token);

Console.WriteLine("Press enter to stop the task"); 
Console.ReadLine(); 
cancellationTokenSource.Cancel(); 

In this case, the operation will end when cancellation is requested and the Task will have a RanToCompletion state. If you want to be acknowledged that your task has been cancelled, you have to use ThrowIfCancellationRequested to throw an OperationCanceledException exception.

Task task = Task.Run(async () =>             
{                 
    while (!token.IsCancellationRequested) {
         Console.Write("*");                      
         await Task.Delay(1000);                
    }           
    token.ThrowIfCancellationRequested();               
}, token)
.ContinueWith(t =>
 {
      t.Exception?.Handle(e => true);
      Console.WriteLine("You have canceled the task");
 },TaskContinuationOptions.OnlyOnCanceled);  
 
Console.WriteLine("Press enter to stop the task");                 
Console.ReadLine();                 
cancellationTokenSource.Cancel();                 
task.Wait(); 

Hope this helps to understand better.

Mahbubur Rahman
  • 4,961
  • 2
  • 39
  • 46
24

You can create a Task with cancellation token, when you app goto background you can cancel this token.

You can do this in PCL https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle

var cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(async () => {
    await Task.Delay(10000);
    // call web API
}, cancelToken.Token);

//this stops the Task:
cancelToken.Cancel(false);

Anther solution is user Timer in Xamarin.Forms, stop timer when app goto background https://xamarinhelp.com/xamarin-forms-timer/

evandrix
  • 6,041
  • 4
  • 27
  • 38
Jesse Jiang
  • 955
  • 6
  • 8
  • 4
    Two problems. First, use [Task.Run()](https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html "StartNew() Is dangerous - Stephen Cleary"). Second, this will not cancel the underlying awaited Task the way you think it will. In this case, you would need to pass `cancelToken` to the delay: `Task.Delay(10000, cancelToken)`. Cancellation via token is cooperative. It needs to be passed down to every awaitable that you would like to be able to cancel in the chain. – Marc L. Sep 08 '20 at 16:40
  • 2
    Regarding point #2 above, [see section "CancellationToken" here](https://blog.stephencleary.com/2015/03/a-tour-of-task-part-9-delegate-tasks.html). – Marc L. Sep 08 '20 at 16:47
12

You can use ThrowIfCancellationRequested without handling the exception!

The use of ThrowIfCancellationRequested is meant to be used from within a Task (not a Thread). When used within a Task, you do not have to handle the exception yourself (and get the Unhandled Exception error). It will result in leaving the Task, and the Task.IsCancelled property will be True. No exception handling needed.

In your specific case, change the Thread to a Task.

Task t = null;
try
{
    t = Task.Run(() => Work(cancelSource.Token), cancelSource.Token);
}

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Titus
  • 149
  • 1
  • 8