Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching exception from worker thread in the main thread

I didn't find a concise answer to the following problem:I have a producer - consumer threading model where main thread is the consumer while some worker thread is the producer.The producer thread runs it's thread loop during the application execution and it is possible for it to throw exceptions occasionally.The main thread is UI thread which should pop-up exception messages including those coming from different threads. How can I catch these exceptions in the main thread?

Using boost on Windows with 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
              }

         }


 }

Important to note that I have no ability to wrap WorkerThread in the main thread with try{}catch as it is created at some point and from then on runs on its own till the application termination.

like image 365
Michael IV Avatar asked Aug 13 '14 09:08

Michael IV


4 Answers

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.

like image 130
Jonathan Wakely Avatar answered Sep 28 '22 03:09

Jonathan Wakely


Those answer suggest you to send exception_ptr to main thread manually. That's not bad way, but I suggest you another way: std::promise / boost::promise.

(Since I don't have boost in this computer now, so I'll go with std::promise. However, there may be no big difference with boost.)

Look the example code:

#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 */
}

The benefit of this way is 1. you don't have to manage exceptions manually - std::promise and std::future will do everything and 2. you can use all feature around std::future. In this case, I'm doing other things (outputing waiting... message) while waiting the thread exit, through std::future::wait_for.

like image 30
ikh Avatar answered Sep 28 '22 03:09

ikh


In the worker thread, you can catch the exception, and then retrieve a std::exception_ptr using std::current_exception. You can then store this somewhere, pick it up in the main thread, and throw it with std::rethrow_exception.

like image 31
Robert Allan Hennigan Leahy Avatar answered Sep 28 '22 02:09

Robert Allan Hennigan Leahy


Exceptions are synchronous. Which means there is no way to pass them between threads as exceptions. You cannot tell to any old thread "stop whatever you are doing and handle this". (Well you can if you deliver a POSIX signal to it, but that's not quite a C++ exception).

You can of course always pass an object with the exception data (as opposed to the state of being in an exception-handling mode) to another thread in the same way you would pass any other data between threads. A concurrent queue will do. Then you process it in the target thread. The target thread should be actively reading data from the queue.

like image 22
n. 1.8e9-where's-my-share m. Avatar answered Sep 28 '22 03:09

n. 1.8e9-where's-my-share m.