Posted at

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

More than 3 years have passed since last update.


例外の転送とは

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

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

非同期になると、親で受け取りにくくなるため

使いにくい気もしますが

一応これで例外を転送できます