'How to start tasks after cancelling

I have a Windows form with three buttons. One button adds entries to a BlockingCollection. One starts processing the list and one stops processing the list.

I can add entries to my BlockingCollection and when I click Start, the list is consumed as I would expect. I can still add new items and they continue to be consumed. However when I click the Stop button, although the tasks DO stop, I cannot start them again with the start button.

What am I doing wrong in the cancellation of the tasks, that they won't start again? I've read countless items on cancelling tasks but still don't 'get it'.

Any help would be great. Here's the code...

    // Blocking list for thread safe queuing
    private BlockingCollection<QueueItem> ItemList = new BlockingCollection<QueueItem>();
    private CancellationTokenSource CancelTokenSource = new CancellationTokenSource();
    private int MaxConsumers = 3;

    // Form initialisation
    public MainForm()
    {
        InitializeComponent();
    }

    // Create an async consumer and add to managed list
    private void CreateConsumer(int iIdentifier)
    {
        Task consumer = Task.Factory.StartNew(() =>
        {
            foreach (QueueItem item in ItemList.GetConsumingEnumerable())
            {
                Console.WriteLine("Consumer " + iIdentifier.ToString() + ": PROCESSED " + item.DataName);
                Thread.Sleep(894);

                if (CancelTokenSource.IsCancellationRequested)
                    break; // Cancel request
            }
        }, CancelTokenSource.Token);
    }

    // Add a new item to the queue
    private void buttonAdd_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < 10; i++)
        {
            Task.Factory.StartNew(() =>
            {
                // Add an item
                QueueItem newItem = new QueueItem(RandomString(12));

                // Add to blocking queue
                ItemList.Add(newItem);
            });
        }
    }

    // Start button clicked
    private void buttonStart_Click(object sender, EventArgs e)
    {
        buttonStart.Enabled = false;
        buttonStop.Enabled = true;

        // Create consumers
        for (int i = 0; i < MaxConsumers; i++)
        {
            CreateConsumer(i);
        }
    }

    // Stop button clicked
    private void buttonStop_Click(object sender, EventArgs e)
    {
        CancelTokenSource.Cancel();

        buttonStop.Enabled = false;
        buttonStart.Enabled = true;
    }

    // Generate random number
    private static Random random = new Random((int)DateTime.Now.Ticks);
    private string RandomString(int size)
    {
        StringBuilder builder = new StringBuilder();
        char ch;
        for (int i = 0; i < size; i++)
        {
            ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
            builder.Append(ch);
        }

        return builder.ToString();
    }


Solution 1:[1]

You re-use the same cancellation token that is still configured to cancel.

So create a new token when starting new tasks.

Solution 2:[2]

Do not instantiate CancellationTokenSource in the class itself. As when you will call CancellationTokenSource.Cancel(), the task will be cancelled, hence when you will try to restart the task again, it will not start as it is cancelled by the CancellationToken of CancellationTokenSource and its scope is throughout the class.

See here:

Task consumer = Task.Factory.StartNew(YourTaskObject, cancellationTokenSource.Token);

// Similarly for Task.Run()
Task consumer = Task.Run(YourTaskObject, cancellationTokenSource.Token);

Hence, modify your code as:

 // Instantiate it as null in the beginning
CancellationTokenSource cancellationTokenSource;

private void CreateConsumer(int iIdentifier)  // Your method 
{
    if (cancellationTokenSource == null || cancellationTokenSource.IsCancellationRequested)
    {
        // This is the simplest form to initialize as a new object initially or after cancelling task everytime
        cancellationTokenSource = new CancellationTokenSource();  
    }

    ... // Rest of the code
}

This also works with Task.Run().

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2