2

I am building a C# / Windows Forms application.

Inside the click event handlers for various buttons on a form, I initialize and fire off different tasks. However, for certain button clicks, I want to cancel out any tasks that are still running that were started by certain other click event handlers.

Below is my code. The second version is my attempt so far at getting the second method to cancel out a running task started by the first method, however it does not work yet. How can I cancel the running Task?

Example Code (no cancellationtokens added yet):

private void btnFrontDoorCycle_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        // Do function 1
        // Do function 2
        // etc
    });
}

private void btnFrontDoorClose_Click(object sender, EventArgs e)
{
    // If task started in btnFrontDoorCycle_Click is running, cancel it here
    Task.Factory.StartNew(() =>
    {
        // Do function 5
        // Do function 6
        // etc
    });
}

Example Code (my non-functioning attempt at adding in cancellationtokens):

private CancellationTokenSource cancellationTokenSource;
private void btnFrontDoorCycle_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        // Do function 1
        // Do function 2
        // etc
    }, cancellationToken);
}

private void btnFrontDoorClose_Click(object sender, EventArgs e)
{
    // If task started in btnFrontDoorCycle_Click is running, cancel it here
    if (this.cancellationTokenSource != null)
    {
        this.cancellationTokenSource.Cancel();
    }
    this.cancellationTokenSource = new CancellationTokenSource();
    CancellationToken cancellationToken = this.cancellationTokenSource.Token;
    Task.Factory.StartNew(() =>
    {
        // Do function 5
        // Do function 6
        // etc
    });
}
Collin Barrett
  • 2,441
  • 5
  • 32
  • 53

2 Answers2

3

Are you checking cancellationToken.IsCancelationRequested inside your code? I think that is the problem.

It is should be something like that:

// Do function 1
if (token.IsCancellationRequested)
{
    return;
}
// Do function 2
// Were we already canceled?
ct.ThrowIfCancellationRequested();// another variant
// etc

More details: https://msdn.microsoft.com/en-us//library/dd997396(v=vs.110).aspx

NIBERIUM
  • 76
  • 6
  • 1
    Ok, hmm. well, some of the functions being called inside of the task are wait functions. I want to cancel the task as soon as the other button is clicked. So, to do this, I basically need to put 'ct.ThrowIfCancellationRequested();' in between every function call in the task to cancel? Isn't there a way to cancel a task no matter where it is in execution by cancelling the token without putting in all of those one-liners to check if its been cancelled yet? – Collin Barrett Aug 27 '15 at 13:09
  • @cbarrett The short answer is: "No", see [this other related question](http://stackoverflow.com/q/4359910) for some insights. It comes down to managing communication between the different contexts - which is exactly what the cancellation token is for. If you're not checking it at least occasionally in the spawned task, then you're throwing away the communication mechanism you setup. It is also why you should pass the token to any other blocking calls in the spawned task if possible. – Anthony Aug 27 '15 at 13:17
  • You'd often want to set *safe* places in your code to cancel (i.e. not in the middle of writing a file, or equivalent). At these safe places you can check if it's been cancelled. If you're worried about still having the first task running while next task starts, keep a reference to the first task and check the `IsCancelled` property from the second. – Erresen Aug 27 '15 at 13:20
  • Thanks, all. I'm still not fully grasping what I need to do. These event handlers will occur multiple times. If I cancel a cancellation token on click, how can I make sure I re-create a cancellation token to be used for the next instance? From my research, it looks like I can't reuse a token once it's cancelled... – Collin Barrett Aug 27 '15 at 14:53
3

You need to check if the token has been cancelled. Below is a quick bit of code I wrote just to check how it works. Hopefully you'll be able to extract what you need from it...

internal class TaskCancellationProblem
{
    private CancellationTokenSource tokenSource;
    private CancellationToken token;

    public TaskCancellationProblem()
    {
        ResetSourceAndToken();
    }

    private void ResetSourceAndToken()
    {
        tokenSource = new CancellationTokenSource();
        token = tokenSource.Token;
    }

    public void RunFirstTask()
    {
        // check if cancellation has been requested previously and reset as required
        if (tokenSource.IsCancellationRequested)
            ResetSourceAndToken();

        Task.Factory.StartNew(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.WriteLine("Doing first task");
                Thread.Sleep(1000);
            }
        }, token);
    }

    public void CancelFirstAndRunSecond()
    {
        // Cancel the task that was running
        tokenSource.Cancel();
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                Console.WriteLine("Doing second task");
                Thread.Sleep(1000);
            }
        });
    }
}
Erresen
  • 1,923
  • 1
  • 22
  • 41
  • thanks, I like this solution. except, when cancelling the token, that token is now dead, correct? What if the event handler "RunFirstTask()" gets run multiple times? How do I recreate new cancellation tokens? – Collin Barrett Aug 27 '15 at 13:51
  • A CancellationTokenSource isn't a factory, it's a one time deal, so you'd probably want to have a method to reinstatiate the source and token... I'll make an edit. – Erresen Aug 27 '15 at 15:00