The Concurrency Runtime and Visual C++ 2010: Transporting Exceptions between Threads

Last time, we looked at how to use the rvalue references in Visual C++ 2010 to help you improve the performance of applications that use the Concurrency Runtime (see The Concurrency Runtime and Visual C++ 2010: Rvalue References). This week we’ll examine how to deal with exceptions that are thrown by the bodies of concurrent tasks.

Consider the following program that contains a Concurrency::parallel_for loop. The loop produces an error condition during one of its iterations:

    1: // uncaught-parallel-loop-error.cpp
    2: // compile with: /c /EHsc
    3: #include <ppl.h>
    4:  
    5: static_assert(false, "This example illustrates a non-recommended practice.");
    6:  
    7: // Placeholder for a function that performs some work.
    8: void do_work(int n)
    9: {
   10:    // For illustration, throw an exception when n exceeds 99.
   11:    if (n >= 100) {
   12:       throw std::invalid_argument("n must be less than 100");
   13:    }
   14: }
   15:  
   16: int wmain() 
   17: {
   18:    // Perform some work in parallel.
   19:    Concurrency::parallel_for(0, 101, [](int i) {      
   20:       do_work(i);
   21:    });
   22:  
   23:    // BUG: The exception goes unhandled, terminating the application.
   24: }

 

How might you handle this error condition? One possibility is to place a try-catch block around the call to do_work and handle the error directly in the loop body. But what if you require for the overall operation to fail if any single operation fails, such as in a transaction-based system? At first glance, there doesn’t appear to be an appropriate place for you to handle this error.

Thankfully, Visual C++ 2010 provides support for transporting an exception from one thread to another. The Parallel Patterns Library (PPL) utilizes this feature in its exception handling model for features such as tasks and parallel algorithms (the parallel algorithms in the PPL are actually built upon tasks). In short, if any iteration of a parallel loop throws, the runtime propagates the exception to the thread that calls parallel_for. Therefore, you can simply add a try-catch block around the parallel_for operation to encapsulate the error:

    1: // caught-parallel-loop-error.cpp
    2: // compile with: /EHsc
    3: #include <ppl.h>
    4: #include <iostream>
    5:  
    6: // Placeholder for a function that performs some work.
    7: void do_work(int n)
    8: {
    9:    // For illustration, throw an exception when n exceeds 99.
   10:    if (n >= 100) {
   11:       throw std::invalid_argument("n must be less than 100");
   12:    }
   13: }
   14:  
   15: int wmain() 
   16: {
   17:    try
   18:    {
   19:       // Perform some work in parallel.
   20:       Concurrency::parallel_for(0, 101, [](int i) {      
   21:          do_work(i);
   22:       });
   23:    }
   24:    catch (const std::exception& e)
   25:    {
   26:       // Report the error.
   27:       std::wcerr << e.what() << std::endl;
   28:    }
   29: }

 

This modified example prints “n must be less than 100” to the console.

An additional benefit in this feature is that when one iteration produces an unhandled exception, the runtime cancels all other active loop iterations and also prevents any additional iterations from starting. Because an exception represents an unrecoverable condition, this prevents unnecessary work from being performed.

The best thing about this feature is that you don’t have to know much about the details in order to make efficient use of it. But if you’re interested in the details, check out Transporting Exceptions Between Threads.

To learn more about the overall exception handling model in the Concurrency Runtime, see Exception Handling in the Concurrency Runtime. This article explains how the runtime works with exceptions that occur in task groups, parallel algorithms, lightweight tasks, and agents.

Well, this completes the tour of new features in Visual C++ 2010 that make it even more delightful to use the Concurrency Runtime to write parallel code. Let us know in the Comments section how you use your favorite new feature with the Concurrency Runtime, and what you’d like to see in the future.

Many thanks to Stephan T. Lavavej, Vinod Koduvayoor Subramanian, Dana Groff, and Tim Allen for their valuable technical insights and help in preparing this material.

Happy coding!