非同期ブーム第二段です。
書き換え方
func_by_cb( $cb, @params ) みたいな形式があった時に、Coroのrouse_cbを使うと、通常の関数呼び出しの形式で呼び出せる関数に変換ができます。こんな感じで。
sub func_by_coro { func_by_cb( Coro::rouse_cb, @_ ); return Coro::rouse_wait; }
変換前の関数(func_by_cb)は、以下のように使います。
func_by_cb( sub { print @_, "\n"; }, 1, 2, 3);
対して、変換後の関数(func_by_coro)では、この処理を以下のように書けます。
print func_by_coro(1,2,3), "\n";
使い勝手は一目瞭然ですね!
コールバックがAnyEventで実現されている場合
コールバックがAnyEventのメインループを使って実現されている場合(ほとんどの場合がこうでしょう)は、もう一捻りが必要です。なぜかと言うと、この書き換えでは rouse_wait で別スレッドに処理を譲ってコールバックを待とうとするのですが、この段階ではreadyなスレッドがなくて「deadlock detected」となるからです。コールバックを呼んでもらうには、rouse_wait後にAnyEventのメインループを進めてもらう必要があります。
そこで、「use Coro::AnyEvent;」をしてやります。こうすると、 $Coro::idle が書き換えられ、readyなスレッドがない時にdeadlockする代わりに、メインループを回しつつreadyなスレッドを探してくれるようになります。
いやあ、よくできてますね。
余談: rouse_cbとrouse_waitの原理
rouse_cbとrouse_waitは、自分で書くことも出来ます。先ほどのfunc_by_coroを、rouse_cbとrouse_waitを使わずに書くと大体こんな感じです*1。
sub func_by_coro{ my @params = @_; my $current = $Coro::current; my $done = 0; my @ret; func_by_cb( sub { @ret = @_; $done = 1; $current->ready; }, @params ); schedule while !$done; return @ret; }
【9/24追記】podを見習って、$doneフラグをつけた。CoroはCoro::State::listで露出されてるので、他スレッドから簡単にreadyできる。よってこの方がいいと思われる。
さらに余談: AnyEventだけでも書き換えられる気がする
ここまで書いておいてなんですが、CoroじゃなくてもAnyEventで同じような書き換えができることに気がつきました。
sub func_by_coro { my $cv = AE::cv; func_by_cb( sub { $cv->send(@_); }, @_ ); return $cv->recv; }
となると、この書き換えに関してはCoroを使う利点はないかもしれません。
【9/22 追記】この書き方をすると、コールバックから関数を呼んだときに「recursive blocking wait detected」となります。podにも「This condition can be slightly loosened by using Coro::AnyEvent」とありますので、素直にCoroの力を借りた方が良さそうです。
*1:wantarrayとか端折ってるので注意。