'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 |