-1

I was reading a book on Parallel Programming and I coded the following scenario which worked as expected (Task.Status == TaskStatus.Canceled):

It’s possible for a task to end in TaskStatus.Canceled. For this to occur, you must pass a CancellationToken as an argument to the factory method that created the task. If that token signals a request for cancellation before the task begins to execute, the task won’t be allowed to run. The task’s Status property will transition directly to TaskStatus.Canceled without ever invoking the task’s user delegate.

But I couldn't get expected result (Task.Status != TaskStatus.Canceled) for the following:

If the token signals a cancellation request after the task begins to execute, the task’s Status property will only transition to TaskStatus.Canceled if the user delegate throws an Operation CanceledException and that exception’s CancellationToken property contains the token that was given when the task was created.

Code

  public void DoWork(int millisec, CancellationToken token, int n )
    {
        ParallelOptions option = new ParallelOptions { CancellationToken = token };

        Parallel.For(0, n, option, (i) =>
        {
           // Thread.Sleep(millisec);
            token.ThrowIfCancellationRequested();
        });
    }

private void btnSearch_Click(object sender, EventArgs e)
{                        
   CancellationToken token = cts.Token;

   t1 = Task.Factory.StartNew(() => DoWork(700, token, 99999999), token);
   t2 = Task.Factory.StartNew(() => DoWork(5, token, 1), token);

   Task[] tasks = new Task[] { t1,t2 };

   int completedTaskIndex = Task.WaitAny(tasks);
   lblMsg.Text = "Task No." + ++completedTaskIndex + " has been completed.\n";

   cts.Cancel();
        Thread.Sleep(5000); // Updated
   lblMsg.Text += "Rest of the tasks have been cancelled.\n";

   try
   {
       lblMsg.Text += "Task 1 Status: " + t1.Status + "\n";
       lblMsg.Text += "Task 2 Status: " + t2.Status + "\n";

       Task.WaitAll(tasks);
       lblMsg.Text += "No exceptions has been reported yet.\n";
   }
   catch(AggregateException ae)
   {

           ae.Flatten().Handle((ex) =>
           {
          // if you debug to see the token handle it is the same as passed to Task.Factory.StartNew()
               if (ex is OperationCanceledException)
           {
               CancellationToken tok = ((OperationCanceledException)ex).CancellationToken;
               return true;
           }
           else
           {
               return false;
           }
       }
       );
   }
   finally 
   {
     if(cts!= null)  
         cts.Dispose();

     cts = null;
   }
}

Result

Before Update:

enter image description here

After Update:

enter image description here

Dicussion

Task 1 is the task which is cancelled so its Status should have been Cancelled instead of Running as shown in figure above.

Question

Is there any understanding gap that I could not get desired result? Where did i lose the track?

Updated (Issue Resolved)

The problem was that I was reading the status of task 1 before it was updated to Cancelled. So I wrote Thread.Sleep(5000); just after cts.Cancel(); so that there's enough time for the task to be cancelled and the status be updated appropriately. That let the status of task 1 to show "Cancelled".

Sadiq
  • 786
  • 1
  • 10
  • 35
  • How did `lblMsg.Text += "Task 1 Status: " + t1.Status + "\n";` generate a label text of "... before wait..." ? – Caius Jard Dec 06 '17 at 12:57
  • You may have issued the cancel but task 1 is probably still in its `Sleep`. It hasn't woken up yet to observe the changed state of the token. – Damien_The_Unbeliever Dec 06 '17 at 12:59
  • And also, when did task 1 get any time to process its cancellation in between cancel being invoked (while it was sleeping?) and its status being read? Maybe you should read the status at the end of the code, or at least after WaitAll ? – Caius Jard Dec 06 '17 at 12:59
  • Thread.Sleep() seemed like a bone of contention so I have commented it out. And changed the loop argument passed in Task. Now task-1 has to loop 50000 times while task 2 has to loop just once. Still Status of Task-1 is Running – Sadiq Dec 06 '17 at 13:09

1 Answers1

0

Taking a look at the program flow, in comments, assuming it takes 1ms to process each line:

   int completedTaskIndex = Task.WaitAny(tasks); //Time T=0
   lblMsg.Text = "Task No." + ++completedTaskIndex + " has been completed.\n"; //time T=5, because we waited 5ms for task 2, right?

   cts.Cancel(); //Time T=6, task 1 is sleeping for another 694 ms
   lblMsg.Text += "Rest of the tasks have been cancelled.\n"; //Time T=7

   try
   {
       lblMsg.Text += "Task 1 Status: " + t1.Status + "\n";//Time T = 8, task1 is sleeping for another 692 ms, current status is "running"
       lblMsg.Text += "Task 2 Status: " + t2.Status + "\n";//Time T=9

       Task.WaitAll(tasks);//Time T=701, task 1 has finished sleeping, and finds out it is cancelled
       lblMsg.Text += "No exceptions has been reported yet.\n";//we probably don't hit this line/see it in label, because cancellation exception is thrown by task 1
   }
   catch(AggregateException ae) //what's task1's status now?

Ultimately I think this is one fo those cases where if you'd jsut coded up the real world task, the UI that has a "go" button that changes to "cancel" before you start your thousand db queries or whatever, then things would have worked out.. but this artificial test you've arranged isn't really representative of the real life cancellation scenario

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • I have commented out Thread.Sleep(). Please go through my edited version. Still task-1 is in Running state – Sadiq Dec 06 '17 at 13:34
  • It's not really what I was advising – Caius Jard Dec 06 '17 at 15:05
  • I understand the fact that while sleeping, thread could not entertain any cancellation request but as soon as the sleep time elapse it does qualify for cancellation and hence caught in the Catch block.Nevertheless, in both the cases, with or without Thread.Sleep(), the cancelled task in the middle of running state maintains its status to "Running" even after being cancelled. When the status should have been "Cancelled". – Sadiq Dec 07 '17 at 18:07
  • I still think with the code as you have it, the job is running when its status is checked, you note in the label text that it is running, and then you never check again or update the label text after that... – Caius Jard Dec 07 '17 at 18:25
  • Status could be checked only before Task.Wait() - and that is what i have already done in the code. However, could not check after that because due to Cancellation exception lines of codes written after Task.Wait() would never execute. Suggest me where else could I update the label? – Sadiq Dec 08 '17 at 07:40
  • But that's the thing - let's say you have a long running database query that after 30seconds gives an error. If you check its status after 1 second and store it in a label as "status:ok" then 29 seconds later it explodes, you can't come complaining "the label says it's fine but it clearly blew up.." - the system told you the truth at the time it was asked?! – Caius Jard Dec 08 '17 at 14:53
  • Thanks got it! Your example actually helped me understand that. – Sadiq Dec 09 '17 at 04:15