LoginSignup
12
14

More than 5 years have passed since last update.

thread、coroutineの 例外を転送する!!

Posted at

例外の転送とは

C++を含む多くの言語では、子スレッドの例外を親が拾う事は出来ない
スレッド毎に例外のコンテキストが別なので、他のスレッドで例外を拾うには
例外を転送しなければならない

子スレッドの例外は親でcatch出来ない
#include <iostream>
#include <thread>
#include <exception>


int main(){

  try{
    std::thread t([](){
      try{
        throw std::exception();    
      }catch(...){
        std::cout << "catch child" << std::endl;
        throw;
      }
    });


    t.join();

    }catch(...){
        std::cout << "catch parent" << std::endl;
    }

    return 1;
}

catch child
terminate called after throwing an instance of 'std::exception'

親で例外をハンドル出来ないので実行時エラーで落ちる。

boost.asioのコルーチンでは?

boost.asioはコルーチンを使うことが出来る。軽量スレッド。とても優秀
しかし このコルーチンも、例外を転送できなかった

boost.asioのコルーチン
#include <iostream>
#include <exception>

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/bind.hpp>

using namespace std;
using namespace boost::asio::ip;

int main(){
    boost::asio::io_service io_service;

        try {
            boost::asio::spawn( io_service, [](boost::asio::yield_context yield ) {

                try {
                    throw std::exception();
                } catch (...) {
                    cout << "catch child" << endl;
                    throw;
                }
            });

        } catch (...) {
            cout << "catch parent" << endl;
        }


        boost::asio::signal_set signals(io_service);
        signals.add(SIGINT);
        signals.add(SIGTERM);
#if defined(SIGQUIT)
        signals.add(SIGQUIT);
#endif // defined(SIGQUIT)
        signals.async_wait(boost::bind(
                &boost::asio::io_service::stop, &io_service));

        io_service.run();

    return 1;
}

spawnで、コンテキスト yieldを作成し、ラムダ関数でコルーチン実行している
下のspawn以下は この際無視して大丈夫

上のspawnで、ラムダ内で例外を発生させ、spawnの前後でcatchしようとしているが
これも上記と同じく、子供の例外を親が受け取れない

これは恐らく、コルーチンの実装もスレッドと同じく、例外コンテキストが個別になっているからだと思われる

例外を転送する(thread)

C++で例外を転送すには std:: exception_ptrを使います

exception_ptr(thread)

#include <iostream>
#include <thread>
#include <exception>
#include <stdexcept>



int main(){

  std::exception_ptr ep;

  try{
    std::thread t([&ep](){
      try{
        throw std::exception();    
      }catch(...){
        std::cout << "catch child" << std::endl;
        ep = std::current_exception();
      }
    });


    t.join();
    if(ep){
      std::rethrow_exception(ep);
    }

  }catch(...){
    std::cout << "catch parent" << std::endl;
  }

    return 1;
}

このようにすると

catch child
catch parent

と、親に例外を転送できました!!

boost.asio コルーチンで行う

やることは同じですが、asioは非同期に設計さていて、spawn後に関数抜けてしまうので
無理やりですが 確認のために 無限ループコルーチンを作り
そこで確認してみます。。。

コルーチン
#include <iostream>
#include <exception>

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/bind.hpp>
#include <stdexcept>

using namespace std;
using namespace boost::asio::ip;

int main(){
  boost::asio::io_service io_service;

  std::exception_ptr ep;


    boost::asio::spawn( io_service, [&ep](boost::asio::yield_context yield ) {

        try {
            throw std::exception();
        } catch (...) {
            cout << "catch in" << endl;
            ep = std::current_exception();
        }
    });

    // 無限ループ
    boost::asio::spawn(io_service, [&ep,&io_service](boost::asio::yield_context yield) {

        try {
            boost::asio::deadline_timer timer(io_service);
            for (; ;) {
                timer.expires_from_now(boost::posix_time::millisec(100));
                timer.async_wait(yield);
                if(ep){
                  std::rethrow_exception(ep);
                }


            }
        }catch(...){
            std::cout << "catch other" << std::endl;
            return;
        }
    });


    boost::asio::signal_set signals(io_service);
    signals.add(SIGINT);
    signals.add(SIGTERM);
#if defined(SIGQUIT)
    signals.add(SIGQUIT);
#endif // defined(SIGQUIT)
    signals.async_wait(boost::bind(
            &boost::asio::io_service::stop, &io_service));

    io_service.run();

    return 1;
}

catch child
catch other

例外が転送できましたね。

非同期になると、親で受け取りにくくなるため
使いにくい気もしますが
一応これで例外を転送できます

12
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14