Perl Hackers Hub

第2回 AnyEventでイベント駆動プログラミング (2)

この記事を読むのに必要な時間:およそ 5 分

ウォッチャー

AnyEventでプログラムを作成する場合「ウォッチャー」を作成,管理することが基本的な作業となります。ウォッチャーとはI/Oやタイマーなどの何かしらのイベントが発生したことを通知してもらうためのオブジェクトです図2のコールバックの指定および実行の部分を担当します)⁠

現在から5秒後にコールバック関数を呼び出してもらうにはリスト1のようなコードを書きます。

リスト1 ウォッチャー

use strict;
use AnyEvent;

my $cv = AnyEvent->condvar; ……(1)

# タイマーウォッチャーを作成
my $w; $w = AnyEvent->timer(
    after => 5, # 今から5秒経ったらイベント発生
    cb => sub { # イベント発生時にこの関数が呼ばれる
        warn "5秒経ちました!";
        undef $w; ……(2)
        $cv->send;
    }
);

$cv->recv; ……(3)

リスト1(1)のように$wに入れた変数をコールバック関数の中で明示的に解放しているのは,コールバック関数が実行されるまで$wの中身をメモリ解放せずに有効にしておきたいからです。AnyEvent->timer関数によって作成されたウォッチャーオブジェクトはメモリ解放されてしまうとイベントが発生しなくなってしまうので,このように明示的に解放するタイミングをコントロールしてあげる必要があるのです。

リスト1(1)で生成している$cvはコンディション変数Condition Variableと呼ばれ,これも一種のイベント通知のために使われます。リスト1の場合,(3)で$cv->recvが呼ばれた時点でイベントループにコントロールが渡り,$cv->sendが呼ばれるまでこのあとのコードが実行されることはありません。つまり$wに指定したコールバック関数が実行されるまで待つ,といった状態になります。コンディション変数にはもっとほかの使い方もありますが,それについては後述します。

AnyEventにはこのタイマーのほかにもいくつかのウォッチャーオブジェクトが存在します。書き出してみるとその種類が意外と少ないことに驚かれるかもしれませんが,実際にコードを書いてみるとこれだけでほとんどのことができることがわかります。

それではどのようなウォッチャーがあるのか見てみましょう。

タイマーウォッチャー

書式

AnyEvent->timer(
    after => $seconds,
    interval => $seconds,
    cb => $cb

);

まずは先述したタイマーウォッチャーです。このウォッチャーは任意のタイミングでイベントを発生させます。リスト2の場合,10秒後にイベントを発生させ,警告を出力してから終了します。

リスト2 タイマーウォッチャー

my $w; $w = AnyEvent->timer(
    after => 10,
    cb => sub {
        warn "10秒経ったよ!";
        undef $w;
    }
);

タイマーは任意のタイミングだけでなく,定期的にイベントを発生させることもできます。リスト3ではまずウォッチャーを作成し,(1)で指定したとおり10秒後にイベントを発生させます。最初のイベントが起こると,そのあと(2)で指定したように1秒ごとに再度イベントが発生します。

リスト3 一定間隔でタイマーを発動

my $count = 0;
    my $w; $w = AnyEvent->timer(
    after => 10, ……(1)
    interval => 1, ……(2)
    cb => sub {
        $count++;
        warn "タイマー発動! ($count回目)";
        if ($count >= 10) {
            undef $w;
        }
    }
);

タイマーを使用する際に重要なのは,タイマーは正確にN秒後(またはN秒おき)にイベントを発生させるのではなく,最低N秒経ったあとにイベントが発生するということです。ほとんどの場合これは誤差の範囲ですが,イベント駆動プログラミングの場合は正確な時間にイベントが発生される保証はないことを念頭に置いてタイマーを使用する必要があります。

I/Oウォッチャー

書式

AnyEvent->io(fh => $fh, poll => $mode, cb => $cb);

I/OウォッチャーはI/Oイベントを監視します。任意のハンドルが書き込み/読み込み可能になったかどうかの判断を行ってくれます。

fhには監視するファイルハンドルを指定します。pollにはそのファイルハンドルに対して読み込み/書き込みイベントのどちらを監視するのかを"r"(読み込み)"w"(書き込み)で指定します。cbはイベント発生時に呼ばれるコールバック関数を指定します。コールバックは引数を受け取りません(バックエンドによっては引数を渡される場合がありますが,仕様には定義されていないのでこれに依存してはいけません)⁠

リスト4の場合,(1)で指定した$fhが書き込み可能(2)"w"を指定したため)になりしだいコールバック関数を呼び出し,文字列を書き込むというウォッチャーを作成しています。

リスト4 I/Oウォッチャー

my $fh = ....; # ソケットなど

my $io; $io = AnyEvent->io(
    fh => $fh, ……(1)
    poll => "w", ……(2)
    cb => sub {
        # 実際に使う際は,syswriteのエラーなどを
        # もっと確認してください
        syswrite( $fh, "hoge hoge hoge" );
        undef $io;
    }
);

これを使ってソケットなどから読み込み可能になったら読み込めるだけ読み込むようなコードを書く場合はリスト5のようにします。

リスト5 I/Oウォッチャーでソケットからデータを読み込む

my $io; $io = AnyEvent->io(
    fh => $fh,
    poll => "r",
    cb => sub {
        my $len = sysread( $fh, my $buf, 8192 );
        if ($len > 0) {
            print "read '$buf'\n";
        } elsif (defined $len) {
            # ハンドルがcloseされた
            undef $io;
        } elsif ( $! != EAGAIN && $! != EINTR ) {
            # よくないエラー
            undef $io;
            die "An error occurred: $!";
        }
    }
);

このしくみを利用して複数のホストに同時接続しながらそれぞれの接続が読み込み可能になったところからコンテンツをダウンロードするということもできますが,それについてはもっと便利なライブラリAnyEvent::HTTPがあるので後述します。

著者プロフィール

牧大輔(まきだいすけ)

オープンソース技術を使ったシステム開発をメインに,講師,執筆活動などを行う。2011年,Perlコミュニティに多大な貢献をもたらした人物に贈られるWhite Camel Awardを受賞。本業の傍ら2009年からのほぼ全てのYAPC::Asia Tokyoで主催をつとめ,その中で現運営母体のJapan Perl Associationも設立。LINE等を経て現在HDE, Inc.勤務。

コメント

コメントの記入