我没有找到以下问题的简洁答案:我有一个生产者-消费者线程模型,其中主线程是消费者,而一些工作线程是生产者。生产者线程在应用程序执行期间运行它的线程循环,它可能偶尔抛出异常。主线程是UI线程,应该弹出异常消息,包括来自不同线程的异常消息。如何在主线程中捕获这些异常?
在Windows上使用c++ 0x
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,因为它是在某个时刻创建的,并且从那时起自己运行直到应用程序终止。
首先,您不需要将bind
与thread
一起使用。这样做只会增加不必要的复制,并使代码更难阅读。我希望每个人都不要再那样做了。
WorkerThread::WorkerThread(){
m_thread = boost::thread(&WorkerThread::drawThread, this);
}
你可以在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;
}
然后在另一个线程中重新抛出它并处理它:
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
通常被工作线程锁定的时间长短,您可能需要为主工作循环的异常队列使用单独的互斥锁(并将m_mutex
锁移动到try
块中)。
另一种方法是使用c++ 11的future,它可以更方便地处理线程间的异常传递。您需要一些方法让主线程获得工作线程运行的每个工作单元的未来,这可以通过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
中,直到通过相关的future读取结果。
// other thread:
auto fut = workerThread.post(&someDrawingFunc, arg1, arg2);
...
// check future for errors
try {
fut.get();
} catch (const std::exception& e) {
// handle it
}
生产者线程可以在向消费者发送工作时将future
对象存储在队列中,并且其他一些代码片段可以检查队列中的每个future是否准备好并调用get()
来处理任何异常。
这些答案建议您手动将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::promise
和std::future
会做所有的事情。您可以在std::future
周围使用all功能。在本例中,我正在通过std::future::wait_for
等待线程退出时做其他事情(输出waiting...
消息)。
在工作线程中,您可以捕获异常,然后使用std::current_exception
检索std::exception_ptr
。然后,您可以将其存储在某个地方,在主线程中拾取它,并使用std::rethrow_exception
抛出它。
异常是同步的。这意味着没有办法将它们作为异常在线程之间传递。你不能告诉任何旧线程"停止你正在做的事情并处理这个"。(如果你向它传递一个POSIX信号,你可以,但这不是c++的异常)。
当然,你总是可以像在线程间传递任何其他数据一样,将带有异常数据的对象(与处于异常处理模式的状态相反)传递给另一个线程。一个并发队列就可以了。然后在目标线程中处理它。目标线程应该主动地从队列中读取数据。