none
yield文でイベントを待機するライブラリ RRS feed

  • 全般的な情報交換

  • (本ライブラリをCodePlexでプロジェクト化しました : http://yieldawait.codeplex.com/)

     

    コードの途中で実行を一時停止できるyeild文を使って、指定したイベントの発生をコードの途中で待機できるようにするライブラリを作りました。どこに投稿すればよいか分からず、場外れでしたらすみません。この場でいくつかご意見いただければと思い、ここにディスカッションで投稿しました。

    [ダウンロード]

    ライブラリは以下のリンクからダウンロードできます。

    EventWaitingCore.zip:
    http://cid-6d750b1d390978fd.office.live.com/self.aspx/Public/1%20EventWaitingCore.zip

    [使い方]

    この中で、ライブラリ自体は「WaitCore.cs」ファイル1つに集約しています。(その他の詳細はこちらに説明があります) ライブラリは次のように使って、コードの途中でイベントを待機できるようにします。

    using SimonPG.WaitCore; // ←[A]
    
    IEnumerable TestFunc(EventWaiter waiter) { // ←[B]
     ...処理A...
     yield return waiter.Wait(button1, "Click"); // ←[C]
     ...処理B...
    }
    void Form_Load(object sender, EventArgs e) {
     new EventWaiter(TestFunc); // ←[D]
    }
    

    ライブラリの名前空間を「SimonPG.WaitCore」としてあるので、始めに[A]のようにインポートします。このライブラリの中で中心的なクラスはEventWaiterクラスです。このクラスを[D]のように引数に関数を指定してインスタンス化すると、その関数の中ではyield文を使ってイベントを待機できるようになります。イベントを待機するには、[C]のようにEventWaiterクラスの入ったwaiter変数からWait関数を呼び出し、引数に待機したいオブジェクトとイベント名を指定します。そして、Wait関数の戻り値をyield returnの形で返します。

    このコードの動作は次のようになります。まずフォームがロードされると、[D]のインスタンス化から内部でTestFunc関数が呼び出されて[A]に来ます。すると、「処理A」のコードがまず実行されて、[C]の行でbutton1のClickイベントが発生するまで待機します。Button1がクリックされると、待機が完了し「処理B」が実行されて、すべての処理が完了します。

    [便利な場合の例]

    コードの途中で待機できると、いろいろと便利な場合があります。例えば、Timerを使ってグラデーションしながら点滅表示する例を考えると、EventWaiterクラスを使ってコードの途中で待機すれば、次のように状態変数を使わずにスマートにコードを書けます。(コードの詳細はこちらに説明があります)

    using SimonPG.WaitCore
    
    IEnumerable TestFunc(EventWaiter waiter) {
     while (true) {
     for (var bright = 0; bright < 255; bright += 10) {
     label1.BackColor = Color.FromArgb(
      bright, bright, bright);
     yield return waiter.Wait(timer1, "Tick");
     }
     for (var bright = 255; bright > 0; bright -= 10) {
     label1.BackColor = Color.FromArgb(
      bright, bright, bright);
     yield return waiter.Wait(timer1, "Tick");
     }
     }
    }
    void Form_Load(object sender, EventArgs e) {
     new EventWaiter(TestFunc);
    }
    

    [いろいろな待機]

    このライブラリにはイベントの発生をただ待機するだけでなく、他にも複数のイベントをAND/OR/NOT結合して待機できたり、指定回数発生したイベントを待機できたり、いろいろな拡張機能があります。詳しくはこちらに説明があります。

    [参考]

    http://simonpg.web.fc2.com/Pages/labo/idea_p/20091001_UseYieldForWait/index.html
    http://simonpg.web.fc2.com/Pages/labo/idea_p/20090213_StopCodeWithWaitKeyword/index.html

     

    • 編集済み Simon.P.G 2011年1月7日 7:02
    2010年6月30日 21:17

すべての返信

  • Reactive Extensions for .NET (Rx) はご存知でしょうか?
    なにかかぶるところがあるような気がしました。
    2010年7月1日 1:15
  • ぜんぜん知りませんでした。情報ありがとうございます。

    eventのLINQ?... 面白そうな気がしますが、とりあえずChannel9のビデオを見てみます。

    2010年7月1日 13:51
  • リンク先は参照できないので、書かれている内容から思ったことだけ


    待機のハンドリングはもう少し抽象化したほうがいいかと思います。できれば標準的な型も利用されたほうがよいでしょう。たとえば待機に利用する同期オブジェクトの生成と、イベントハンドラの設定を分離して、

    this.timer1.Timer += waiter.CreateEvent("wait1");
    this.button1.Click += waiter.CreateEvent("wait2");
    
    var optEvent = waiter.CreateEvent("wait3");
    this.button1.Click += optEvent;
    
    /* 処理A */
    yield return waiter.Wait("wait1");
    /* 処理B */
    yield return waiter.Wait("wait2");
    /* 処理C */
    yield return waiter.Wait("wait1").And("wait3");
    
    /* 処理D */
    if (...)
    {
     this.button1.Click -= optEvent;
     this.button2.Click += optEvent;
    }
    yield return waiter.Wait("wait1").Or("wait3");
    
    2010年7月2日 4:05
  • ありがとうございます。すみません、配置しなおしました。こちらからダウンロードお願いします。

    http://cid-6d750b1d390978fd.office.live.com/self.aspx/Public/1%20EventWaitingCore.zip

    分離した方がよいのは、最後の部分で挙げてある例のように、this.button1.Clickからthis.button2.Clickに変更したい場合があるからという認識でよいでしょうか。なるべく、待機のためのコードは簡潔にしたかったのでWait(...)一行に収めましたが、なるほど、分離すると、例えばoptEventをクラスのメンバに渡して外部に公開した場合、"wait3"を待機している間に外部で待機対象を変更できるようになりますね。

    このライブラリ(EventWaitingCore.zip)もいろいろ拡張を考えて、例えば

     

    yield return waiter.Wait(new Or(new Event(button1, "Click"), new Event(button2, "Click")));

     

    のようなコードも書けるようにしてあるんですが(詳細こちら)、確かにメソッドチェーンの方がAnd/Orが見やすいと感じます。

    2010年7月2日 10:02
  • Reactive Extensions for .NET (Rx)に関するPDCの動画を見て、佐祐理さんの言うRxと似ている点がやっと理解できました。

    動画: LINQ, Take Two – Realizing the LINQ to Everything Dream

    動画の53分ぐらいから始まる「await」新キーワードの動作は、ここで言う「yield return waiter.Wait」と、享受したい利点が同じでした。違う点は、「await」は非同期のTaskの終了を待機するのに対して、ここでの「yield return waiter.Waitは同じスレッド内のイベント発生を待機します。

    今のところ、「yield return waiter.Waitは同期イベントの待機しか出来ませんが、同期/非同期どちらでも待機できるように、今拡張を考えています。また、スレッドの種類(Task or Thread or Windowメッセージループを持つスレッドなど)も気にすることなく待機できれば、と考えています。

    やっと理解できたので、Rxをこれまで以上に注視していきたいと思います。
    2010年11月17日 11:42
  • 私もまだ勉強中なのですが、

    awaitはスレッドを規定しません。System.Concurrency名前空間には各種スケジューラークラスがあり、例えばCurrentThreadSchedulerクラスを使えば同じスレッドで処理します。他にもTaskPoolSchedulerやThreadPoolSchedulerとかいろいろ。WinForm向けのControlSchedulerとWPF向けのDispatcherSchedulerも。

    2010年11月17日 11:54
  • Rxすごいですね。やりたいことが全部出来そうでびっくりです。

    Schedulerクラスを使えば、スレッドの種類に柔軟に対応できそうですね。

    PDCの動画ではタスクの終了を待機していましたが、Rxの仕組みで送られたイベントなども待機できるのでしょうか。

    2010年11月21日 1:58
  • 本ライブラリをCodePlexでプロジェクト化しました : http://yieldawait.codeplex.com/
    2011年1月7日 7:02