2

I'm new to the TPL (Task-Parallel Library) and am wondering if the following is the most efficient way to spin up 1 or more tasks, collate the results, and display them in a datagrid.

  1. Search1 & Search2 talk to two separate databases, but return the same results.
  2. I disable the buttons and turn on a spinner.
  3. I'm firing the tasks off using a single ContinueWhenAll method call.
  4. I've added the scheduler to the ContinueWhenAll call to update form buttons, datagrid, and turn off the spinner.

Q: Am I doing this the right way ? Is there a better way ?
Q: How could I add cancellation/exception checking to this ?
Q: If I needed to add progress reporting - how would I do that ?

The reason that I chose this method over say, a background worker is so that I could fire each DB task off in parallel vs. sequentially. Besides that, I thought it might be fun to use the TPL.. however, since I could not find any concrete examples of what I'm doing below (multiple tasks) I thought it might be nice to put it on here to get the answers, and hopefully be an example for others.

Thank you!

Code:

//  Disable buttons and start the spinner
btnSearch.Enabled = btnClear.Enabled = false;
searchSpinner.Active = searchSpinner.Visible = true;

//  Setup scheduler
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();

//  Start the tasks
Task.Factory.ContinueWhenAll(
  //  Define the search tasks that return List<ImageDocument>
  new [] {  
    Task.Factory.StartNew<List<ImageDocument>>(Search1), 
    Task.Factory.StartNew<List<ImageDocument>>(Search2) 
  }, 
  //  Process the return results
  (taskResults) => {
    //  Create a holding list
    List<ImageDocument> documents = new List<ImageDocument>();
    //  Iterate through the results and add them to the holding list
    foreach (var item in taskResults) {
      documents.AddRange(item.Result);
    }
    //  Assign the document list to the grid
    grid.DataSource = documents;
    //  Re-enable the search buttons
    btnSearch.Enabled = btnClear.Enabled = true;
    //  End the spinner
    searchSpinner.Active = searchSpinner.Visible = false;
  }, 
  CancellationToken.None, 
  TaskContinuationOptions.None, 
  scheduler
);
Matthew M.
  • 3,422
  • 4
  • 32
  • 36
  • 2
    I wrote a blog article on thread-safe events and Tasks using TPL here that you may want to look at: http://www.dcomproductions.com/blog/2011/08/writing-thread-safe-event-handlers-with-the-task-parallel-library-in-net-4-0/ – David Anderson Aug 22 '11 at 23:55
  • Nice article David. Thank you! I've actually implemented that pattern, after learning the hardway.. that you can't pass a scheduler into your "main thread" without it locking! I figured out that the only way to do it was to launch a new task within the main Task to do UI updating, which is what you're doing. In the example above - I didn't realize that the scheduler object I was passing in -- was passing into the continuation object, not the tasks listed at the start! Fun stuff -- still, much easier than other methods, except maybe async/await which I'm not yet using. – Matthew M. Aug 23 '11 at 15:22
  • The link to David Anderson's article has changed to http://www.danderson.me/2011/08/writing-thread-safe-event-handlers-with-the-task-parallel-library-in-net-4-0/ – Magnus Lindhe Jun 28 '12 at 11:32
  • The link has changed again: http://danderson.io/posts/writing-thread-safe-event-handlers-with-the-task-parallel-library-in-net-4-0/ No matter how many times you change it, we will track it down :) – Memet Olsen Feb 26 '20 at 11:31

2 Answers2

2

Q: Am I doing this the right way ? Is there a better way ?

Yes, this is a good way to handle this type of situation. Personally, I would consider refactoring the disable/enable of the UI into a separate method, but other than that, this seems very reasonable.

Q: How could I add cancellation/exception checking to this ?

You could pass around a CancellationToken to your methods, and have them check it and throw if a cancellation was requested.

You'd handle exceptions where you grab the results from taskResults. This line:

  documents.AddRange(item.Result);

Is where the exception will get thrown (as an AggregateException or OperationCanceledException) if an exception or cancellation occurred during the operations.

Q: If I needed to add progress reporting - how would I do that ?

The simplest way would be to pass the scheduler into your methods. Once you've done that, you could use it to schedule a task that updates on the UI thread - ie: Task.Factory.StartNew with the TaskScheduler specified.


however, since I could not find any concrete examples of what I'm doing below (multiple tasks)

Just FYI - I have samples of working with multiple tasks in Part 18 of my series on TPL.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Very nice article(s), thanks Reed. Appreciate the look-over - I agree on moving the UI code out.. did not to make the question shorter. Did not see your articles -- would not have typed in Tasks with continuations. Maybe you could add some keywords like "Multiple tasks TPL UI sync" etc. so it comes up in results for those new to TPL that may be searching for the /wrong/ keywords. :) – Matthew M. Jul 20 '11 at 20:00
0

For best practices, read the Task-Based Asynchronous Pattern document. It includes recommendations on cancellation support and progress notification for Task-based APIs.

You'd also benefit from the async/await keywords in the Async CTP; they greatly simplifiy task continuations.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank's for the link Stephen, had not seen that document. Yes, am aware of async/await - but am not going to be using them until they're out of CTP ! :) – Matthew M. Jul 20 '11 at 20:27