C++:使用boost::exception处理跨线程异常的问题

4

基本上,我的情况是一个线程抛出了一个异常,而另一个线程需要处理它。我试图使用boost exception来实现这个功能,但在某个地方异常失去了它的类型,因此未被catch块捕获。

基本上,线程B想要做一些事情,但由于各种原因,必须使用线程A(如果您想知道这些原因,请问MS为什么直接3d 9设备必须由创建窗口的同一线程创建、重置和释放)。如果在执行这些操作时发生异常,线程A会捕获它,将其传递回线程B,然后重新抛出以根据需要进行处理。问题是在线程B中抛出的异常似乎与在线程A中抛出的异常不同。:(

下面是我的程序的调试输出和代码。

0x776b42eb处第一次异常:fllib::exception::Error在内存位置0x0019e590处。
0x776b42eb处第一次异常:[rethrow]在内存位置0x00000000处。
0x776b42eb处第一次异常:boost::exception_detail::clone_impl<boost::unknown_exception>在内存位置0x0019eed4处。
//thread B
...
try
{
    SendCallback(hwnd, boost::bind(&Graphics::create, this));
}
catch(fllib::exception::Error &except)//example catch block, doesnt catch example exception
{
    ...handle exception...
}

void SendCallback(HWND hwnd, boost::function<void()> call)
{
    boost::exception_ptr *except_ptr = 
        (boost::exception_ptr*)SendMessage(hwnd, WM_CALLBACK, (unsigned)&call, SEND);
    if(except_ptr)//if an exception occurred, throw it in thread B's context
    {
        boost::exception_ptr except = *except_ptr;
        delete except_ptr;
        boost::rethrow_exception(except);
    }
}
//thread A
LRESULT CALLBACK HookProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    //check for our custom message
    if(msg == WM_CALLBACK)
    {
        if(lParam == POST)
        {
            ...
        }
        else
        {
            boost::function<void()> *call = (boost::function<void()>*)wParam;
            try
            {
                (*call)();
            }
            catch(...)
            {
                return (unsigned)new boost::exception_ptr(boost::current_exception());
            }
            return 0;
        }
    }
    else
    {
        ...
    }
}

void Graphics::create()
{
...code that may throw exceptions...
eg
    throw fllib::exception::Error(L"Test Exception...");
}

@Neil Butterworth 为什么你删除了 Visual Studio 调试输出的一部分??? - Fire Lancer
也许需要尝试使用throw来抛出异常: throw boost::enable_current_exception(my_exception()); http://www.revergestudios.com/boost-exception/enable_current_exception.html - bayda
我不完全理解为什么你不想在捕获的异常或包装的捕获的异常中返回指针,而是从钩子过程中不进行常规的抛出操作?请提供注释,说明函数是从A线程调用还是从B线程调用。 - bayda
从 catch 块返回指向异常的指针是安全的吗?我以为 C++ 一旦代码离开 catch 块,除非遇到 throw; 否则会删除异常对象。无论如何,即使这是有效的,那么在线程 B 中抛出相同类型的异常会导致什么结果? - Fire Lancer
我至少可以假设异常可以被复制和复制构造,我相信...至少我认为所有的std::和boost::异常都可以被复制和复制构造? - Fire Lancer
显示剩余6条评论
4个回答

2
确保Thread A中的每个throw都使用boost::enable_current_exception包装器。请参见this

"除非在使用throw表达式时调用enable_current_exception,否则尝试使用current_exception复制异常对象可能会返回一个引用未知异常实例的exception_ptr。"

为此,您可能需要在Thread A中捕获所有异常,并在使用throw boost::enable_current_exception(your_exception)包装后重新抛出它们。
另外,这对于结构化异常(如除以零)不起作用,除非您使用_set_se_translator调用包装器并将其包装在异常对象中。

"要做到这一点,我怎么能在不为每种可能的异常类型编写catch块的情况下实现呢?" - Fire Lancer

1

我找到了自己的异常类型的解决方案。

基本上,我在所有我的异常类型中添加了2个虚拟方法。

  • virtual Base* Clone(); 克隆异常并返回堆中的新异常。这可以避免离开catch块时原始异常被销毁的问题。
  • virtual void ThrowAndDelete(); 这会创建堆分配的异常对象的本地副本,删除堆对象(以防止内存泄漏),然后引发堆栈副本。这具有作为最派生类型抛出的优点。

这意味着现在我可以简单地执行:

void SendCallback(HWND hwnd, boost::function<void()> call)
{
    fllib::exception::Base *except_ptr = 
        (fllib::exception::Base*)SendMessage(hwnd, WM_CALLBACK, (unsigned)&call, SEND);
    if(except_ptr) except_ptr->ThrowAndDelete();
}
LRESULT CALLBACK HookProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    //check for our custom message
    if(msg == WM_CALLBACK)
    {
        if(lParam == POST)
        {
            ...
        }
        else
        {
            boost::function<void()> *call = (boost::function<void()>*)wParam;
            try
            {
                (*call)();
            }
            catch(fllib::exception::Base &except)
            {
                return (unsigned)except.Clone();
            }
            return 0;
        }
    }
    else
    {
        ...
    }
}

关于 std:: 和 boost:: 异常的处理,我不确定该怎么做,因为它们似乎没有上述方法的效果...然而,95%以上可能无法提前处理的异常是我的自定义类,而那些不是的几乎肯定会被忽略...


1
  1. 使用 BOOST_THROW_EXCEPTION 抛出异常。

  2. 在线程中使用 catch(...),然后调用 boost::current_exception 将异常(无论是什么)放入 boost::exception_ptr 中。

  3. 将 exception_ptr 复制到主线程,然后调用 rethrow_exception。这将重新抛出在线程中 catch(...) 捕获的异常对象。

请参阅www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html。


0

我认为你有问题,因为没有使用boost方式抛出异常。

你可以在线程A的钩子过程中捕获具体的异常,将其复制到新对象中并返回指针。
在SendCallback中检查结果,如果结果存在(不为NULL),则抛出异常。
但是你必须确保如果没有异常,始终返回NULL。


问题是我需要为每种异常类型都编写一个catch块,这意味着至少包括所有的std::、boost::以及我的代码使用的所有异常类型?然后,我该如何将指针转换回正确的类型,而不是抛出一个无法预期的void*异常? - Fire Lancer
良好的实践是让所有异常派生自std::exception。 - bayda
你可以做如下操作: some_error_info* result = 0; try { // your code here } catch (const thirdparty::exception& e) { result = new your_own_error_type(e); } catch (/* exceptions from boost */) { // do same } catch (const std::exception& e) { // do same } catch (...) { result = new unknown_error(); } return result; - bayda
但是这回到有成千上万的dynamic_casts和throws...我的异常是std::exception -> fllib::exception::Base -> ...,我假设boost也是这样的吧?问题在于只有大约1%的代码实际上捕获了std::exception,而那些捕获的代码只是作为最后一道防线来优雅地崩溃。 - Fire Lancer
所以我需要一种方法来返回到抛出原始类型...但我看不到任何方法可以返回到原始类型,除非对每种可能的类型使用动态转换...例如,如果(FileNotFound *e = dynamic_cast<FileNotFound>(except))throw *e;if(next... - Fire Lancer
即使如此,它仍需要大量的维护才能保持最新状态...难道没有办法克隆std::exception&,然后稍后作为最派生类型抛出吗?(例如,假设它捕获并克隆了一个最终从std::exception继承的DllLoadError异常,我需要重新抛出为DllLoadError...) - Fire Lancer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接