【问题标题】:Catching exception from worker thread in the main thread从主线程中的工作线程捕获异常
【发布时间】:2014-10-06 14:29:30
【问题描述】:

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

在带有 C++0x 的 Windows 上使用 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
              }

         }


 }

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

【问题讨论】:

  • 亲爱的互联网,您不需要将bindthread 一起使用。就说m_thread = boost::thread(&amp;WorkerThread::drawThread, this);

标签: c++ multithreading boost


【解决方案1】:

首先,您不需要将bindthread 一起使用。这样做只会增加不必要的复制并使代码更难阅读。我希望每个人都不要这样做。

WorkerThread::WorkerThread(){

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

}

您可以将异常存储在 exception_ptr 中并将其传递给另一个线程,例如在std::queue&lt;std::exception_ptr&gt;:

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;
}

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

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';
    }
}

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

您可能希望对主工作循环中的异常队列使用单独的互斥锁(并将m_mutex 锁移动到try 块内),具体取决于工作线程通常锁定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
    }
}

当任务运行时,任何异常都将被捕获,存储在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
}

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

【讨论】:

  • +1 表示packaged_task。人们跳到裸std::thread 的速度太快了,而对于许多目的来说它实际上太低级了。
  • 这样,需要posting 才能捕获异常,不是吗? zz..顺便说一句,我认为这种方法与boost::asio::io_service::work 非常相似
  • @Jonathan Wakely 您在示例中使用 lock_guard 有什么特殊原因吗?
  • @ikh,不,调用post 只能在创建packaged_task 或将其添加到队列时内存分配失败时抛出异常。该任务在drawThread 中运行,因此任何异常都会发生在那里并被packaged_task 捕获并存储在future 中。
  • @MichaelIV,你有什么理由使用unique_lock?您需要延迟锁定吗?还是提前发布?还是将其移至另一个对象?如果不是,为什么要使用更复杂的锁类型? lock_guard 只是锁定和解锁,仅此而已,这就是代码所需的全部内容。
【解决方案2】:

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

(由于我现在这台电脑没有boost,所以我就用std::promise。不过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 */
}

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

【讨论】:

  • promise 的问题是你只能使用一次,问题显示在循环中捕获异常,这意味着它需要在第一次之后继续处理异常
  • 使用future 的方法是生产者以std::packaged_task 对象的形式将工作传递给消费者,保持与每个packaged_task 共享状态的future .这样,每个发布给消费者的任务都有一个相应的未来,以及一个让异常返回的渠道
  • @JonathanWakely packaged_task??我不明白..你能解释更多吗?
  • 我在答案中添加了另一个示例,使用 packaged_task
【解决方案3】:

在工作线程中,您可以捕获异常,然后使用std::current_exception 检索std::exception_ptr。然后你可以把它存储在某个地方,在主线程中提取它,然后用std::rethrow_exception 扔掉它。

【讨论】:

  • 我的意思是你说的我已经在几个地方读过,但没有一个真正解释如何做到这一点。
  • “怎么做”是什么意思?你得到一个std::exception_ptr,将它传递给主线程,然后重新抛出它。
  • 我如何在主线程中检索它?伙计,如果你想回答,请花点时间用例子进行简洁的解释。
  • 与将任何对象从一个线程传递到另一个线程的方式相同。有多种选择,最好取决于您的要求。由互斥锁保护的std::queue&lt;std::exception_ptr&gt; 是一种方法。
【解决方案4】:

异常是同步的。这意味着无法将它们作为异常在线程之间传递。你不能告诉任何旧线程“停止你正在做的任何事情并处理它”。 (如果你向它传递一个 POSIX 信号,你可以,但这不是一个 C++ 例外)。

当然,您总是可以将带有异常数据的对象(与处于异常处理模式的状态相反)传递给另一个线程,就像在线程之间传递任何其他数据一样。并发队列就可以了。然后在目标线程中处理它。目标线程应该正在积极地从队列中读取数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 2010-12-27
    • 1970-01-01
    相关资源
    最近更新 更多