前エントリー:http://blogs.wankuma.com/myugaru/archive/2008/03/10/127092.aspx
siokoshouさんのコメントを拝見しまして私の説明力・説得力の無さを痛感しました。
ちょっと悔しいので今日は別の切り口で説明のリベンジをしたいと思います。
(また失敗に終わる予感もしますが・・・)
話はWindowsプログラミングです。
ボタン1をクリックすると画面の中央に
「ボタン1が押されました」
という文字が0.5秒間隔で点滅し、
ボタン2を押すとそれが消える
と仮定してください。
以下のようにプログラムを作ったとします。
ボタン1クリック()
{
while (ボタン2フラグ == OFF) {
while (0.5秒間)
「ボタン1が押されました」表示
while (0.5秒間)
//何もしない
}
}
ボタン2クリック()
{
ボタン2フラグ = ON
}
一見仕様は満たされています。しかし上のプログラムは上手く動作しません。
ボタン1をクリックしてしまうと画面が真っ白になってしまうことでしょう。
誰でも一度くらいは経験があるフリーズという状態です。
タスクの切替が考慮されていないために起こる現象です。
改良したボタン1クリックが次のプログラムです。
ボタン1クリック()
{
while (ボタン2フラグ == OFF) {
while (0.5秒間) {
「ボタン1が押されました」表示
DoEvents();
}
while (0.5秒間) {
DoEvents();
}
}
}
DoEventsはここではWindowsへ制御を渡す役割をする関数と思ってください。
・・・長く説明すると馬鹿にしてるとお叱りを受けますからここでやめます(苦笑
コルーチンで同じことを考えましょう。
ボタン1クリックは次のように書けます。
ボタン1クリック()
{
while (ボタン2フラグ == OFF) {
while (0.5秒間) {
「ボタン1が押されました」表示
yield return 適当;
}
while (0.5秒間) {
yield return 適当;
}
}
}
コルーチンのyieldは状態をレジュームしメインループに制御を戻します。
次回の呼び出し時はyieldの次から再開されます。
DoEventsと全く同じですね。ここは大丈夫ですよね?
さてまたWindowsへ戻ります。
このボタン1クリックを呼び出しているのはWindowsならばWinProcに当ります。
そこにメインループが書かれています。
WinProc()
{
while (true) {
switch (イベント) {
case ボタン1:
ボタン1クリック();
break;
case ボタン2:
ボタン2クリック();
break;
}
}
}
上のコードには実は隠された処理が多く存在しています。
たとえばDoEventでの処理中断再開の機構が見えません。
しかしここで全て書くにはあまりに膨大です。
それにWindowsも一般的になってきたので省略します。
余談ですが。昔々Windows98がまだ健在だった時代。今も健在だとお叱り受けますね^^;;
自分の会社が初めて業務でWindowsを扱うことになった頃に上のような処理で、
「ボタン1の処理中にどうしてボタン2へ飛んでいけるのか」
を何日も何日も何日もかけて上司に説明した事を思い出しています(笑
話を戻します。私が実装を予定しているゲームメインはこうなります。
WinProcでは隠されていたと説明した部分もここでは露見します。
GameLoop()
{
IEnumerator ボタン1タスク = ボタン1クリック();
IEnumerator ボタン2タスク = ボタン2クリック();
List タスクリスト
タスクリスト.Add(ボタン1タスク)
タスクリスト.Add(ボタン2タスク)
while (true) {
foreach (タスク in タスクリスト)(★1)
if (!タスク.MoveNext()) {
//終了したタスクを除外
タスクリスト.Remove(タスク)
}
}
}
}
★1:(15:40追記)実際には私の独自実装をほどこしたLinkedListを用います。
説明の本質から外れるので便宜上foreachを用いましたが現実のC#言語のforeachはノードを削除することはできません。
削除を考慮した独自のLinkedList拡張→http://blogs.wankuma.com/myugaru/archive/2008/03/06/126581.aspx
Windowsでの実装とコルーチンでの実装をご覧いただきました。
両者は
「処理を一旦中断し別タスクへ処理のチャンスを渡す」
という全く同じ基本思想で成り立っています。
●では再度お尋ねします。コルーチンのyieldは何を返すのでしょうか?
コルーチンのyieldが返す値についてもう一度だけ考えてみてほしいと思います。
列挙のyieldではありません。コルーチンのyieldです。
WindowsのDoEventsととらえてこれを再考するといかがでしょうか?
例題を見てもらうと両者の位置付けは全く同一です。
果たしてDoEventsは何を返すべきでしょう?ボタン1クリックの返値ではありませんよ。
yield returnがCurrentを返すと考えると上記でのDoEventsにとってのCurrentに該当するものは一体なんなのでしょうか?
ボタン1の所属する・・この場合はFormになるのでしょうか?
そうでなければ何であれば適当ですか?
またコルーチンを列挙とどうしてもとらえたいという方にはボタン1クリック()というハンドラーを列挙と捕らえることにどういう意味があるかを再考いただければと思います。
前エントリーから私の意見は「コルーチンのyieldの返値は無意味であり不要である」です。
DoEventsだって返値は現実にありませんものね。
お時間ありましたら再度皆さんのご感想などいただけたらうれしく思います。
ここまで読んでお付き合いくださった方に感謝いたします。ありがとうございます。