質問者
yield文でイベントを待機するライブラリ

全般的な情報交換
-
(本ライブラリを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
すべての返信
-
Reactive Extensions for .NET (Rx) はご存知でしょうか?
なにかかぶるところがあるような気がしました。 -
リンク先は参照できないので、書かれている内容から思ったことだけ
待機のハンドリングはもう少し抽象化したほうがいいかと思います。できれば標準的な型も利用されたほうがよいでしょう。たとえば待機に利用する同期オブジェクトの生成と、イベントハンドラの設定を分離して、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");
-
ありがとうございます。すみません、配置しなおしました。こちらからダウンロードお願いします。
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が見やすいと感じます。
-
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をこれまで以上に注視していきたいと思います。 -
本ライブラリをCodePlexでプロジェクト化しました : http://yieldawait.codeplex.com/