7

I have a program that starts a task as follows:

token = this.tokenSource.Token;

var t = Task.Factory.StartNew(() =>
{
    while(!token.IsCancellationRequested)
    {
        // do the work...
    }
}, token);

My goal with this task is to perform some work until the user stops the program. The user stopping the program would involve stopping this task from running (as follows):

this.tokenSource.Cancel();

The thread would then observe the cancelled token-source and therefore stop. I am wondering... How is that any different than doing something like:

private bool isCancelled = false;

public void Start()
{
    var t = Task.Factory.StartNew(() =>
    {
        while (!this.isCancelled)
        {
            // do the work...
        }
    });
}

public void Stop()
{
    this.isCancelled = true;
}

I usually do my programs the first way (which hasn't failed me yet), but I am second guessing myself now for some reason. Are these both bad-practice?

1 Answer 1

5

The second doesn't properly synchronize access to the memory that you're sharing between multiple threads, and as a result, it won't necessarily function the way that you want it to. For example, the thread pool thread doing the work is allowed to see that it isn't writing to that variable (another thread may be, but it doesn't need to know or care about what other threads are doing when there is no explicit synchronization going on) and so it's free to cache that boolean value rather than fetching it from memory each time it needs it, which it's all the more likely to do given how frequently it's reading that value. This will result in it not noticing the change from the other thread, possibly for quite some time. And of course the fact that this problem won't happen every time will make diagnosing it when it does happen all the more difficult.

CancellationToken takes care of all of the necessary synchronization so that you don't have to, and it'll do it as efficiently as possible to boot.

It's also often beneficial from a design standpoint. You can separate who is responsible for canceling the operation from who is responsible for acting on that cancellation. There is much looser coupling between your worker and it's caller/consumer as a result. That worker could do work for anyone that wants it to do work; it's not tying the cancellation to a particular variable in a particular location. This is all the more relevant when the entity responsible for canceling the operation is several layers removed from the entity responsible for doing the work. It's a lot easier to pass a cancellationtoken to something that passes it to something that checks it to see when cancellation happens than to try to manage that with just a boolean; it either means one of those entities needs to know explicitly about the other, despite all of the layers of abstraction between them, or it would require encapsulating that boolean in some way analogous to what CancellationToken does.

Additionally, abstracting out the idea of a cancellation token allows you to create higher level operations that act on/with cancellation tokens. It lets you create a cancellation token that will be cancelled after a set period of time, or to create a cancellation token that is only cancelled when all of a set of tokens are cancelled, or when any of a set of tokens are canceled, etc.

7
  • Hmm... That's helpful to know. Based on what you're saying, I had another thought. Would I have issues trying to produce items into a list and consume them, if the list is just a private member? (doing things similarly to method #1)
    – Snoop
    Sep 12, 2016 at 18:59
  • @StevieV If the list is being accessed from multiple threads at the same time, yes, that will absolutely cause problems. If you need that you'll need to use a collection specifically designed to do just that and handle the synchronization appropriately, such as BlockingCollection. If you're explicitly handling the synchronization, or you otherwise ensure that the list is never being accessed from multiple threads, then it may function correctly. I of course couldn't say without seeing the specifics (which would be off topic for this question).
    – Servy
    Sep 12, 2016 at 19:00
  • I didn't know that existed, so I invented my own with a readonly-object and a Monitor.Wait/Pulse pattern. That seems to work just fine, is that okay instead of using BlockingCollection?
    – Snoop
    Sep 12, 2016 at 19:01
  • 1
    @StevieV Ironic timing. Regarding BlockingCollection: Think of the maintenance programmers. If there are no other programmers, the maintenance programmer is you (actually it's you-from-the-future-who-forgot-everything-about-this-code)
    – Peter
    Sep 12, 2016 at 19:04
  • 1
    @Peter When the name of that boolean is isCancelled I don't think it's confusing. I did think about that one, but honestly the boolean code is perfectly clear about its intentions, when it actually works, and when it's actually practical/appropriate for both the producer and the consider to access the variable.
    – Servy
    Sep 12, 2016 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.