What happens after TParallel.WaitForAny returns?

I was showing off the new Parallel Programming Library (PPL) at a recent event, in particular the demo where we spawn off a couple of tasks and then wait for only one of them to finish. The code in question looked like this:

As you can see, we’re creating two tasks:

  • the first will sleep for 3000 milliseconds (3 seconds) and then add 3000 to a local variable called value
  • the second will sleep for 5000 milliseconds and then add 5000 to value

We start them both and then wait for the first one to finish (on the line where we call TTask.WaitForAny(tasks))

So what will the variable “value” hold immediately after WaitForAny returns? That should be easy. Even allowing for scheduling differences, it should be 3000.

But what you may not realise is the second task is still running. If you don’t believe me, put a breakpoint on the call to Tinterlocked.Add in the second task, debug the method and then leave the ShowMessage dialog up for longer than 2 seconds (giving the second task time to finish) and your breakpoint should fire.

parallel man - http://www.comicvine.com/parallel-man-invasion-america-2/4000-468612/

This is important to understand. Depending on your app, and what you are doing in your tasks, this might matter a lot.  Let’s say we were doing something else with “value” after the call to ShowMessage. It’s possible that between the call to ShowMessage (where value was equal to 3000) and the next line where we were using it, it’s value could become 8000.

So, what should we do if we don’t want this to happen?

Firstly, after WaitForAny returns, we should cancel the other tasks. Some code like the following will loop through all the tasks, cancelling any that have not completed.

where LTask is just a local variable of type ITask.

That’s a good start, but calling Cancel on a Task doesn’t actually cancel the task. That might sound a little odd at first, but all you are really doing is signalling to the task that it should cancel itself. It’s up to the logic inside the anonymous method you pass the task to check for this status.

When should you check it? Depends on what you’re doing, but there are a few likely places:

  • Right at the start of your Task. Remember, you might have tasks that have not yet started, due to waiting for threads to become available in the Thread Pool, so if they have been cancelled before they have even started, may as well find that out early and get out before doing any work.
  • If the task is long running, you might check it at regular intervals, so you can bail out and stop any time- or resource-consuming activity as early as possible. Let’s say you are in a loop, you might check it every time around the loop, or every x times around the loop, depending on the work you are doing.
  • While the above two might be optional in your app, I’d say this one is mandatory. In my opinion you should absolutely check it before you make any changes outside your task, like updating the UI or in this case, writing back to the “value” variable, like so:

So here’s what my revised code looks like in total:

 

My main point with this post was to highlight that Tasks continue on, even if you’re not waiting for them anymore, so checking for the Cancelled flag and ending your tasks as soon as possible is a good thing. However, we’re not quite done here. There is still a potential race condition in this code, which I’ll resolve in the next post.

If you’re looking for more details on the PPL, including some meatier examples, check out Danny Wind‘s CodeRage 9 session, and stay all the way to the end.

5 Comments

  • Most interesting here is that value doesn’t show up in the debugger and you can’t inspect it at all. (Put a break point on value := 0). In fact, it seems any value magically sucked into the anonymous context is unavailable to the debugger.

    • Hmmm, I”m not seeing that problem. I can use the tooltip evaluations, the watch window, right-click and Inspect, and even the Local Variables window shows Self.Value.

      Evaluating the value of Value

      What version of XE7 are you running?

      Cheers
      Malcolm

  • There is still a race condition. If the cancel flag is set after the test but before the interlocked.Add, however small this possibility is, it will result in an additional add.

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">