从主线程中的工作线程捕获异常

Mic*_* IV 4 c++ multithreading boost

我没有找到以下问题的简明答案:我有一个生产者 - 消费者线程模型,其中主线程是消费者,而一些工作线程是生产者.生产者线程在应用程序执行期间运行它的线程循环,这是可能的因为它偶尔抛出异常.主线程是UI线程,它应该弹出异常消息,包括来自不同线程的异常消息.如何在主线程中捕获这些异常?

在Windows上使用C++ 0x的boost

WorkerThread.cpp

WorkerThread::WorkerThread(){

   m_thread = boost::thread(&WorkerThread::drawThread,this);

}

void WorkerThread::drawThread()
{

         while(true)
         {
             boost::unique_lock<boost::mutex> lock(m_mutex);
              try{

                ///some work is done here...

              }catch(std::exception &e){

               /// some exception is thrown
               /// notify main thread of the exception
              }

         }


 }
Run Code Online (Sandbox Code Playgroud)

需要注意的是,我无法使用try {} catch在主线程中包装WorkerThread,因为它是在某个时刻创建的,从那时起自行运行直到应用程序终止.

Jon*_*ely 13

首先,你不需要使用bindthread.这样做只会增加不必要的复制并使代码更难阅读.我希望每个人都会停止这样做.

WorkerThread::WorkerThread(){

    m_thread = boost::thread(&WorkerThread::drawThread, this);

}
Run Code Online (Sandbox Code Playgroud)

您可以将异常存储在一个exception_ptr并将其传递给另一个线程,例如std::queue<std::exception_ptr>:

void WorkerThread::drawThread()
{
    while(true)
    {
        boost::unique_lock<boost::mutex> lock(m_mutex);
         try{

            ///some work is done here...

         }catch(std::exception &e){
             m_queue.push(std::current_exception());
         }
    }
}

std::exception_ptr WorkerThread::last_exception()
{
    boost::lock_guard<boost::mutex> lock(m_mutex);
    std::exception_ptr e;
    if (!m_queue.empty())
    {
        e = m_queue.front();
        m_queue.pop();
    }
    return e;
}
Run Code Online (Sandbox Code Playgroud)

然后在另一个线程重新抛出它并处理它:

if (auto ep = workerThread.last_exception())
{
    // do something with exception
    try
    {
        std::rethrow_exception(ep);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error in worker thread: " << e.what() << '\n';
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你不能使用std::exception_ptrBoost有它自己的实现,但我不确定Boost相当于什么current_exception.您可能需要将异常包装在另一个对象中,以便Boost异常传播机制可以存储它.

您可能希望使用单独的互斥锁作为主工作循环中的异常队列(并m_mutextry块内移动锁定),具体取决于m_mutex工作线程通常锁定的时间长度.


另一种方法使用C++ 11期货,它可以更方便地处理线程之间的异常传递.您需要一些方法让主线程为工作线程运行的每个工作单元获取未来,这可以通过以下方式完成std::packaged_task:

class WorkerThread
{
public:
  WorkerThread();   // start m_thread, as before

  template<typename F, typename... Args>
  std::future<void> post(F f, Args&&... args)
  {
    Task task(std::bind<void>(f, std::forward<Args>(args)...));
    auto fut = task.get_future();
    std::lock_guard<std::mutex> lock(m_mutex);
    m_tasks.push(std::move(task));
    return fut;
  }

  private:
    void drawThread();
    std::mutex m_mutex;
    using Task = std::packaged_task<void()>;
    std::queue<Task> m_tasks;
    std::thread m_thread;
  };

 void WorkerThread::drawThread()
 {
    Task task;
    while(true)
    {
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            task = std::move(m_tasks.front());
            m_tasks.pop();
        }
        task();   // run the task
    }
}
Run Code Online (Sandbox Code Playgroud)

当任务运行时,任何异常都将被捕获,存储在一个exception_ptr并保持,直到通过相关的未来读取结果.

// other thread:

auto fut = workerThread.post(&someDrawingFunc, arg1, arg2);
...
// check future for errors
try {
   fut.get();
} catch (const std::exception& e) {
   // handle it
}
Run Code Online (Sandbox Code Playgroud)

生产者线程可以future在将工作发布到使用者时将对象存储在队列中,而另一些代码可以检查队列中的每个未来以查看它是否已准备好并调用get()以处理任何异常.

  • +1 为`packaged_task`。人们跳到裸`std::thread` 太快了,而出于许多目的,它实际上太低级了。 (3认同)

ikh*_*ikh 5

这些答案建议您exception_ptr手动发送到主线程。这还不错,但我建议你另一种方式:std::promise/ boost::promise

(因为我现在这台电脑没有boost,所以我std::promise就用boost。但是boost可能没有太大区别。)

查看示例代码:

#include <iostream>
#include <exception>
#include <thread>
#include <future>
#include <chrono>

void foo()
{
    throw "mission failure >o<";
}

int main()
{
    std::promise<void> prm;

    std::thread thrd([&prm] {
        try
        {
            std::this_thread::sleep_for(std::chrono::seconds(5));
            foo();
            prm.set_value();
        }
        catch (...)
        {
            prm.set_exception(std::current_exception());
        }
    });

    std::future<void> fu = prm.get_future();
    for (int i = 0; ; i++)
    {
        if (fu.wait_for(std::chrono::seconds(1)) != std::future_status::timeout)
            break;
        std::cout << "waiting ... [" << i << "]\n";
    }

    try
    {
        fu.get();
        std::cout << "mission complete!\n";
    }
    catch (const char *msg)
    {
        std::cerr << "exception: " << msg << "\n";
    }

    thrd.join(); /* sorry for my compiler's absence of std::promise::set_value_at_thread_exit */
}
Run Code Online (Sandbox Code Playgroud)

这种方式的好处是 1. 您不必手动管理异常 -std::promise并且std::future会做所有事情和 2. 您可以使用周围的所有功能std::future。在这种情况下,我waiting...在等待线程退出时正在做其他事情(输出消息),通过std::future::wait_for.