Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

CoroとAnyEventを一緒に使うならunblock_subを覚えておくべき

以前に、

なお、(2)でフロントエンドの処理を書く時は(0)のバックエンドとは逆に、AnyEventは直接使わない方が無難だと思います。理由は、AnyEventのイベントループからはCoroの層の関数が使えないからです。

Coro::AnyEventでフロントエンドをコールバックを使わずに書く

なんて書いてたので、多少追記を。

CoroとAnyEventの相性

これは、非常によいです。

Coroは use Coro::AnyEvent とか明示的にしなくても、内部でAnyEventを使っています。例えば、Coro::Timer::sleepは制御を他スレッドに渡してその後自分のスレッドに戻ってくるという動きをしますが、この「自分のスレッドに戻ってくる」という動作を、AnyEventのイベントループに預けたコールバックから自スレッドのreadyを呼んで起こしてもらうことで実現しています。

Coro::Timer::sleepのようにAnyEventのコールバックを待つ関数は、冒頭で引用したエントリにあるようにCoro::AnyEventとrouse_cb、rouse_waitによって簡単に作れます。実際、Coro::Timer::sleepの実装は以下のように単純です。

sub sleep {
   my $timer = AE::timer $_[0], 0, Coro::rouse_cb;
   Coro::rouse_wait;
}

# http://cpansearch.perl.org/src/MLEHMANN/Coro-5.2/Coro/Timer.pm
# より抜粋

unblock_subの使い方

相性のいいCoroとAnyEventですが、1つだけ注意点があります。それが最初に引用した、

理由は、AnyEventのイベントループからはCoroの層の関数が使えないからです。

ということです。例えば、以下のコードは終了しません。

# このコードは終了しないので注意!
my $done = AE::cv;

my $guard = AE::timer 0, 0, sub {
	print "Started\n";
	Coro::Timer::sleep 0;
	print "Ended\n";
	$done->send;
};

$done->recv;

この問題のため、Coroにはunblock_subという関数が用意されています*1。AnyEventに預けるコールバックをsubではなくunblock_subで定義することで、きちんと終了するようになります。unblock_subは、与えたコードブロックをAnyEventのイベントループではなく、別スレッドを立ち上げて実行してくれます。

my $guard = AE::timer 0, 0, unblock_sub {
	# ... 略 ...
};

CoroとAnyEventを同時に使う場合は、安全のためにunblock_subを使いましょう。

*1:件のエントリを書いた段階では見落としていました