Firstly, you do not need to use bind
with thread
. Doing so just adds unnecessary copying and makes the code harder to read. I wish everyone would stop doing that.
WorkerThread::WorkerThread(){
m_thread = boost::thread(&WorkerThread::drawThread, this);
}
You can store an exception in an exception_ptr
and pass that to the other thread, e.g. in 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;
}
Then in the other thread rethrow it and handle it:
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';
}
}
If you can't use std::exception_ptr
Boost has its own implementation of it, but I'm not sure what the Boost equivalent of current_exception
is. You might need to wrap the exception in another object so the Boost exception propagation mechanism can store it.
You might want to use a separate mutex for the exception queue from the main work loop (and move the m_mutex
lock inside the try
block) depending how long m_mutex
is usually locked by the worker thread.
A different approach uses C++11 futures, which handle passing exceptions between threads more conveniently. You need some way for the main thread to get a future for each unit of work the worker thread runs, which can be done with 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
}
}
When the task is run any exceptions will be caught, stored in an exception_ptr
and held until the result is read through the associated future.
// other thread:
auto fut = workerThread.post(&someDrawingFunc, arg1, arg2);
...
// check future for errors
try {
fut.get();
} catch (const std::exception& e) {
// handle it
}
The producer thread could store the future
objects in a queue when posting work to the consumer, and some other piece of code could check each future in the queue to see if it's ready and call get()
to handle any exception.