异常传输通常是指程序在运行时,从一个线程向另一个线程的通信过程发生异常。
概念异常传输通常是指程序在运行时,从一个线程向另一个线程的通信过程发生异常。通过传输异常,你可以在一个线程中捕获异常,然后使该异常看似是在另一个线程中引发的。 例如,你可以使用该功能编写多线程应用程序,其中主线程将处理其辅助线程引发的所有异常。 传输异常对创建并行编程库或系统的开发人员最有用处。 为了实现传输异常,Visual c++提供了 exception_ptr 类型和current_exception,rethrow_exception和make_exception_ptr 函数。
解决方案假设你要创建能伸缩以处理可变工作负荷的应用程序。 为了实现此目标,你要设计一个多线程应用程序,其中初始的主线程会创建所需数量的辅助线程,以完成该工作。 辅助线程可帮助主线程管理资源、平衡负载和提高吞吐量。 通过分发工作,多线程应用程序的表现优于单线程应用程序1。
但是,如果辅助线程引发异常,你希望主线程予以处理。 这是因为你希望你的应用程序无论有多少辅助线程,都能以一致统一的方式处理异常。
为处理先前的方案,C++ 标准支持在线程之间传输异常。 如果辅助线程引发异常,该异常会成为 当前异常。 拿现实世界打比方,当前异常就好比处于飞行状态。 当前异常从引发之时到捕获它的异常处理程序返回之时就处于飞行状态。
辅助线程可在 catch 块中捕获当前异常,然后调用 current_exception 函数将该异常存储在 exception_ptr 对象中。 exception_ptr 对象必须可用于辅助线程和主线程。 例如,exception_ptr 对象可以是全局变量,由 mutex 控制对它的访问。 传输异常意味着一个线程中的异常可以转换为可以由其他线程访问窗体。
接下来,主线程调用 rethrow_exception 函数,该函数提取并继而引发 exception_ptr 对象中的异常。 异常引发后,将成为主线程中的当前异常。 也就是说,该异常看起来源自主线程。
最后,主线程可以捕获 catch 块中的当前异常,然后进行处理或将其抛给更高级别的异常处理程序。 或者,主线程可以忽略该异常并允许该进程结束。
大多数应用程序不必在线程之间传输异常。 但是,该功能在并行计算系统中有用,是因为该系统可以在辅助线程、处理器或内核间分配工作。 在并行计算环境中,单个专用线程可以处理辅助线程中的所有异常,并可以为任何应用程序提供一致的异常处理模型。
异常处理模型和编译器选项你的应用程序的异常处理模型决定了它是否可以捕获和传输异常。 Visual C++ 支持三种模型,这些模型可以处理 C++ 异常、结构化异常处理 (SEH) 异常和公共语言运行时 (CLR) 异常。 使用 /EH 和 /clr 编译器选项可指定应用程序的异常处理模型2。
只有编译器选项和编程语句的以下组合可以传输异常。 其他组合要么不能捕获异常,要么能捕获但不能传输异常。
/EHa 编译器选项和 catch 语句可以传输 SEH 和 c + + 异常。
/EHa, ,/EHs, ,和 /EHsc 编译器选项和 catch 语句可以传输 c + + 异常。
/CLR 或 /CLR: pure 编译器选项和 catch 语句可以传输 c + + 异常。 /CLR 编译器选项暗含的规范 /EHa 选项。 请注意,编译器不支持传输托管异常。 这是因为托管异常派生自 System.Exception 类, ,已经是可通过使用公共语言运行时的功能的线程间移动的对象。
示例以下示例从一个线程向另一个线程传输标准 C++ 异常和自定义 C++ 异常。
// transport_exception.cpp
// compile with: /EHsc /MD
#include
#include
#include
#include
using namespace std;
// Define thread-specific information.
#define THREADCOUNT 2
exception_ptr aException[THREADCOUNT];
int aArg[THREADCOUNT];
DWORD WINAPI ThrowExceptions( LPVOID );
// Specify a user-defined, custom exception.
// As a best practice, derive your exception
// directly or indirectly from std::exception.
class myException : public std::exception {
};
int main()
{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
// Create secondary threads.
for( int i=0; i {
aArg[i] = i;
aThread[i] = CreateThread(
NULL, // Default security attributes.
0, // Default stack size.
(LPTHREAD_START_ROUTINE) ThrowExceptions,
(LPVOID) &aArg[i], // Thread function argument.
0, // Default creation flags.
&ThreadID); // Receives thread identifier.
if( aThread[i] == NULL )
{
printf("CreateThread error: %d\n", GetLastError());
return -1;
}
}
// Wait for all threads to terminate.
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);
// Close thread handles.
for( int i=0; i CloseHandle(aThread[i]);
}
// Rethrow and catch the transported exceptions.
for ( int i = 0; i try {
if (aException[i] == NULL) {
printf("exception_ptr %d: No exception was transported.\n", i);
}
else {
rethrow_exception( aException[i] );
}
}
catch( const invalid_argument & ) {
printf("exception_ptr %d: Caught an invalid_argument exception.\n", i);
}
catch( const myException & ) {
printf("exception_ptr %d: Caught a myException exception.\n", i);
}
}
}
// Each thread throws an exception depending on its thread
// function argument, and then ends.
DWORD WINAPI ThrowExceptions( LPVOID lpParam )
{
int x = *((int*)lpParam);
if (x == 0) {
try {
// Standard C++ exception.
// This example explicitly throws invalid_argument exception.
// In practice, your application performs an operation that
// implicitly throws an exception.
throw invalid_argument("A C++ exception.");
}
catch ( const invalid_argument & ) {
aException[x] = current_exception();
}
}
else {
// User-defined exception.
aException[x] = make_exception_ptr( myException() );
}
return TRUE;
}
Output
exception_ptr 0: Caught an invalid_argument exception.
exception_ptr 1: Caught a myException exception.
本词条内容贡献者为:
王沛 - 副教授、副研究员 - 中国科学院工程热物理研究所