简体   繁体   English

从主线程中的工作线程捕获异常

[英]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. 我找不到以下问题的简明答案:我有一个生产者-消费者线程模型,其中主线程是消费者,而某些工作线程是生产者。生产者线程在应用程序执行期间运行它的线程循环主线程是UI线程,应该弹出异常消息,包括来自不同线程的异常消息。 How can I catch these exceptions in the main thread? 如何在主线程中捕获这些异常?

Using boost on Windows with C++0x 在Windows上使用C ++ 0x使用Boost

WorkerThread.cpp 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. 需要注意的重要一点是,我无法使用try {} catch将WorkerThread包裹在主线程中,因为它是在某个时刻创建的,从那时开始它自己运行直到应用程序终止。

Firstly, you do not need to use bind with thread . 首先, 您不需要使用bindthread 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, eg in std::queue<std::exception_ptr> : 您可以存储一个例外的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;
}

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. 如果您不能使用std::exception_ptr Boost会有自己的实现,但是我不确定current_exception的Boost等效项是什么。 You might need to wrap the exception in another object so the Boost exception propagation mechanism can store it. 您可能需要将异常包装在另一个对象中,以便Boost异常传播机制可以存储该异常。

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. 您可能要对主队列中的异常队列使用单独的互斥锁(并在try块内移动m_mutex锁),具体取决于工作线程通常将m_mutex锁定多长时间。


A different approach uses C++11 futures, which handle passing exceptions between threads more conveniently. 另一种方法使用C ++ 11 Future,它更方便地处理线程之间的异常传递。 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 : 您需要某种方式使主线程获得工作线程运行的每个工作单元的未来,这可以通过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. 运行任务时,将捕获任何异常,将其存储在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
}

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. 当将工作发布给使用者时,生产者线程可以将future对象存储在队列中,并且其他一些代码段可以检查队列中的每个将来以查看是否就绪,然后调用get()处理任何异常。

Those answer suggest you to send exception_ptr to main thread manually. 这些答案建议您手动将exception_ptr发送到主线程。 That's not bad way, but I suggest you another way: std::promise / boost::promise . 这不是坏方法,但我建议您采用另一种方法: 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.) (由于我现在没有在这台计算机中使用boost,所以我将使用std::promise 。但是,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 . 这种方式的好处是1.您无需手动管理异常std::promisestd::future将完成所有工作,并且2.您可以使用std::future周围的所有功能。 In this case, I'm doing other things (outputing waiting... message) while waiting the thread exit, through std::future::wait_for . 在这种情况下,我在等待线程退出时通过std::future::wait_for做其他事情(输出waiting...消息)。

In the worker thread, you can catch the exception, and then retrieve a std::exception_ptr using std::current_exception . 在辅助线程中,您可以捕获异常,然后使用std::current_exception检索std::exception_ptr You can then store this somewhere, pick it up in the main thread, and throw it with std::rethrow_exception . 然后,您可以将其存储在某个地方,在主线程中进行拾取,然后使用std::rethrow_exception

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). (好吧,如果您向它传递POSIX信号,那不是C ++的例外)。

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. 目标线程应正在从队列中主动读取数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM