从作为分离线程运行的 boost::asio::io_service::work 捕获异常

Posted

技术标签:

【中文标题】从作为分离线程运行的 boost::asio::io_service::work 捕获异常【英文标题】:Catching exception from boost::asio::io_service::work running as a detached thread 【发布时间】:2016-03-05 12:41:42 【问题描述】:

我有我的应用程序主循环控制,我在其中启动一个线程来处理 asio 工作,如下所示:

void AsioThread::Run()

    try
    
        /*
         * Start the working thread to io_service.run()
         */
        boost::asio::io_service::work work(ioservice);
        boost::thread t(boost::bind(&boost::asio::io_service::run, &ioService));
        t.detach();

        while (true)
        

            // Process stuff related to protocol, calling 
            // connect_async, send_async and receive_async
        
    
    catch (std::runtime_error &ex)
    
        std::cout << "ERROR IN FTP PROTOCOL: " << ex.what() << std::endl;
    
    catch (...)
    
        std::cout << "UNKNOWN EXCEPTION." << std::endl;
    

在异步操作期间,处理程序被调用,有时我会在这些处理程序上抛出异常,例如:

void AsioThread::ReceiveDataHandler(const boost::system::error_code& errorCode, std::size_t bytesTransferred)

        std::cout << "Receive data handler called. " << bytesTransferred << " bytes received." << std::endl;

        if (errorCode)
        
            std::cout << "Error receiving data from server." << std::endl;
        

        rxBufferSize = bytesTransferred;

        /* 
         * Check for response
         */
        std::string msg(rxBuffer);
        if (msg.substr(0, 3) != "220")
               throw std::runtime_error("Error connecting to FTP Server");
    

我的问题是异步处理程序 (AsioThread::ReceiveDataHandler) 中抛出的异常没有被AsioThread::Run 中的主处理循环try...catch 块捕获。自然会发生这种情况,因为工作线程 t 在另一个线程上,处于分离状态,并且在运行时会导致执行错误。

如何从分离的boost::asio::io_service::work 线程接收异常?如何构建我的代码以使这个逻辑起作用?

感谢您的帮助。

【问题讨论】:

为什么会有一个单独的 while (true) 事件循环?你可以只使用 asio 事件循环来做所有事情吗?在这种情况下加入线程。如果它可以用完工作,请创建一个 io_service::work 对象。 我需要事件循环,因为它作为协议逻辑的状态处理器......没有它就无法实现。 不确定我是否理解你,但通常协议状态是在异步回调中处理的。不需要为此设置单独的事件循环。例如,在 async_connect 之后的回调中,您将被连接等。事件处理程序启动下一个异步操作,例如async_connect 处理程序也可以开始读取和写入。 是的,这就是正在做的事情,但是整个逻辑运行从客户端类调用,该类向 maon 循环发送和接收命令,如 Connect、SendFile 等......这就是为什么我确实有一个接收这些消息的主循环......就像嵌入协议的命令控制概念...... 为什么异常需要传播到AsioThread::Run()?是否只退出while 循环?您能否提供一个关于while(true) 循环在做什么的代码示例?这个问题感觉像是XY problem,是当前设计的结果。 【参考方案1】:

您可以在工作线程中捕获异常,将它们保存到两个线程共享的队列变量中,并在主线程中定期检查该队列。

要使用队列,您需要先将异常转换为通用类型。您可以使用std::exceptionstring 或任何最适合您情况的方法。如果您绝对需要保留原始异常类的信息,可以使用boost::exception_ptr。

您需要的变量(这些可能是AsioThread 的成员):

boost::mutex queueMutex;
std::queue<exceptionType> exceptionQueue;

在工作线程中运行这个函数:

void AsioThread::RunIoService()
    try
        ioService.run();
    
    catch(const exceptionType& e)
        boost::lock_guard<boost::mutex> queueMutex;
        exceptionQueue.push(e);
    
    catch(...)
        boost::lock_guard<boost::mutex> queueMutex;
        exceptionQueue.push(exceptionType("unknown exception"));
    

像这样启动工作线程:

boost::thread t(boost::bind(&AsioThread::RunIoService, this));
t.detach();

在主线程中:

while(true)
    // Do something

    // Handle exceptions from the worker thread
    bool hasException = false;
    exceptionType newestException;
    
        boost::lock_guard<boost::mutex> queueMutex;
        if(!exceptionQueue.empty())
            hasException = true;
            newestException = exceptionQueue.front();
            exceptionQueue.pop();
        
    
    if(hasException)    
        // Do something with the exception
    

This blog post 实现了一个线程安全的队列,你可以使用它来简化保存异常;在这种情况下,您将不需要单独的互斥锁,因为它位于队列类中。

【讨论】:

这是个好主意,但我没有编写工作线程...它由boost::asio 处理。见boost::thread t(boost::bind(&amp;boost::asio::io_service::run, &amp;ioService));以上... @Mendes 虽然您可能无法控制io_service::run(),但您确实可以控制线程的入口点。考虑创建一个在 try/catch 块中调用 io_service.run() 的函数,根据需要传播异常,并将此函数指定为线程入口点。 @Mendes 我改变了我的答案,它现在应该适用于你的情况。

以上是关于从作为分离线程运行的 boost::asio::io_service::work 捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

线程和任务的分离:线程池技术

Linux线程 | 创建 终止 回收 分离

C++线程分离进程

从分离线程停止主游戏循环的最佳方法

无法从分离的线程访问 C++ unordered_map 中的键值对

linux线程的创建、退出、等待、取消、分离