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!
Comments
Anonymous
March 26, 2011
What happens if multiple exceptions happen? Is there some sort of AggregateException type available?Anonymous
March 26, 2011
What is the best practice if do_work() has a side-effect?Anonymous
April 26, 2011
Hi Tanveer. If a task or parallel algorithm receives multiple exceptions, the runtime marshals only one of those exceptions to the calling context. The runtime does not guarantee which exception it marshals. You can find an example of this on MSDN (see the section 'Multiple Exceptions'): msdn.microsoft.com/.../dd997692.aspxAnonymous
August 08, 2011
How terminated thread releases their mutex files or other resource them gained if one thread throw exception?Anonymous
August 08, 2011
We regularly recommend that you write exception-safe code when writing tasks. I recommend that you use RAII patterns whenever this is a concern. We provide scoped locks for this very reason.