Catching an exception from a worker thread on the main thread

I haven't found a concise answer to the following problem: I have a producer model - a consumer threading model where the main thread is the consumer whereas some worker thread is the producer. The producer thread loops the thread while the application is running and it can sometimes throw exceptions. The main thread is the UI thread that should raise exception popup messages, including those coming from different threads. How can I catch these exceptions on 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
              }

         }


 }

      

It is important to note that I have no way to wrap the WorkerThread in the main thread with try {} catch, since it is created at some point and from that point on it starts by itself until the application ends.

+3


source to share


4 answers


First, you don't need to use bind

withthread

. It just adds unnecessary copying and makes the code harder to read. I wish everyone would stop doing this.

WorkerThread::WorkerThread(){

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

}

      

You can store the exception in exception_ptr

and pipe it to another thread for example. 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, on another thread, collapse it and process 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, but I'm not sure what the Boost equivalent is current_exception

. You may need to wrap the exception in another object so that Boost's exception propagation mechanism can store it.



You might want to use a separate mutex for the exception queue from the main runtime loop (and move the lock m_mutex

inside the block try

) depending on how long the m_mutex

worker thread normally locks for.


Another approach uses C ++ 11 flags that are more convenient for handling exceptions between threads. You need some way for the main thread to get the future for each unit of work of the worker thread, 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 executed, any exceptions will be caught, stored in, exception_ptr

and retained until the result is read through the appropriate future.

// other thread:

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

      

A producer projector could store objects future

in a queue when publishing a work to a consumer, and some other piece of code could check each future in the queue to see if it was ready and call get()

to handle any exception.

+9


source


This answer prompts you to submit exception_ptr

to the main thread manually. It's not bad, but I suggest you another way: std::promise

/ boost::promise

.

(Since I don't currently have a boost on this computer, I'll go with std::promise

. However, there can't be much difference with boost.)

Check out the sample 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 absence of std::promise::set_value_at_thread_exit */
}

      

The advantage of this method is that you do not have to manually manage the exceptions - std::promise

and std::future

will do anything and 2. You can use all the functions around std::future

. In this case, I am doing other things (displaying a message waiting...

) while waiting for the stream to exit through std::future::wait_for

.

+4


source


In a worker thread, you can catch the exception and then retrieve std::exception_ptr

with std::current_exception

. Then you can store that somewhere, pick it up in the main thread and throw it with std::rethrow_exception

.

+1


source


Exceptions are synchronous. This means that there is no way to pass them between threads as exceptions. You cannot say to any old thread, "stop whatever you are doing and get over it." (It's okay if you deliver a POSIX signal to it, but that's not really a C ++ exception.)

Of course, you can always pass an object with exception data (as opposed to state in exception handling mode) to another thread in the same way as any other data between threads. A parallel queue will run. Then you process it into the target theme. The target thread must actively read data from the queue.

+1


source







All Articles