簡體   English   中英

從主線程中的工作線程捕獲異常

[英]Catching exception from worker thread in the main thread

我找不到以下問題的簡明答案:我有一個生產者-消費者線程模型,其中主線程是消費者,而某些工作線程是生產者。生產者線程在應用程序執行期間運行它的線程循環主線程是UI線程,應該彈出異常消息,包括來自不同線程的異常消息。 如何在主線程中捕獲這些異常?

在Windows上使用C ++ 0x使用Boost

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包裹在主線程中,因為它是在某個時刻創建的,從那時開始它自己運行直到應用程序終止。

首先, 您不需要使用bindthread 這樣做只會增加不必要的復制,並使代碼更難以閱讀。 我希望每個人都不要這樣做。

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異常傳播機制可以存儲該異常。

您可能要對主隊列中的異常隊列使用單獨的互斥鎖(並在try塊內移動m_mutex鎖),具體取決於工作線程通常將m_mutex鎖定多長時間。


另一種方法使用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對象存儲在隊列中,並且其他一些代碼段可以檢查隊列中的每個將來以查看是否就緒,然后調用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::promisestd::future將完成所有工作,並且2.您可以使用std::future周圍的所有功能。 在這種情況下,我在等待線程退出時通過std::future::wait_for做其他事情(輸出waiting...消息)。

在輔助線程中,您可以捕獲異常,然后使用std::current_exception檢索std::exception_ptr 然后,您可以將其存儲在某個地方,在主線程中進行拾取,然后使用std::rethrow_exception

異常是同步的。 這意味着沒有辦法在線程之間將它們作為異常傳遞。 您不能告訴任何舊線程“停止所有操作並處理此問題”。 (好吧,如果您向它傳遞POSIX信號,那不是C ++的例外)。

當然,您總是可以將帶有異常數據的對象(而不是處於異常處理模式的狀態)傳遞給另一個線程,就像在線程之間傳遞任何其他數據一樣。 並發隊列可以。 然后在目標線程中對其進行處理。 目標線程應正在從隊列中主動讀取數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM