- - PR -
イベントハンドラーの中でさらにイベントを起こすと、再帰的に呼び出されてしまう
1|2|3|4
次のページへ»
投稿者 | 投稿内容 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2008-04-07 23:04
イベントハンドラーの中でイベントを起こすと、イベントハンドラーが再帰的に呼ばれてしまい、コールスタック(呼び出し履歴)がどんどん深くなっていきます。
これを避けたいのですがどうするのが巧みなコーディングなのでしょうか? たとえば、つぎのようなコードを Console.WriteLine の行にブレークポイントをしかけてデバッガーで実行しボタン(button1)を押す、ブレークポイントを通るたびにコールスタックが深くなっているのが分かります。
この問題について私は、C# や VB(VB6.0やそれ未満) や Java など、どの言語でも、何年も前から悩んでいます。たいていは 0秒後に発火するようなタイマーを使うとうまく回避できるのですが、もっとスマートなやりかたはないものでしょうか? それともタイマーを使うのが良いソリューションだと考えて良いのでしょうか? | ||||||||||||
|
投稿日時: 2008-04-07 23:24
どういうときにそういう設計になってしまうんですか?
具体的な例がほしいです。 | ||||||||||||
|
投稿日時: 2008-04-08 01:56
とりあえず、「イベントドリブン」という概念について、 今一度考えてみることをオススメします。 利点・欠点・典型的な実装などを整理するといいでしょう。 大抵の場合は「イベントドリブン」をきちんと理解していない、 うまく使えていないことが原因であろうと思います。 それほど頻度は多くないですが、 場合によってはスタックがどんどん深くなるような実装にならざるを得ない状況もあることはあります。 #他人のコードをいじるときなど。 そういう場合、その処理は「イベントドリブン」というパラダイムと相性がよくない、ということになります。 どのような処理をしたいのかによって解決策は変わりますが、 別スレッドで処理させるとうまくいったり、 独自のタスクキューイングを実装したり… Timerを使うのもよい方法であろうと思います。 .NetならApplication.Idleを使える場合もあります。 いずれにせよ泥臭い方法です。
イベントドリブンという概念に合うように、 自分の頭にあるアルゴリズムを修正できればスマートでしょう。 | ||||||||||||
|
投稿日時: 2008-04-08 02:39
たとえば、Observer パターンを使うような場合、Observer 自身がたまたま Observable でもある、というような場合があります。そういうときに、自分がたまたま Observable であっただけで、このような再帰問題に悩まされてしまいます。 具体例としては、DBMS のテーブルのように複数のレコードを保持するようなクラスを考えます。レコードAに変更が加えられたら、それを監視する Observer が、レコードBを変更したいような場合があります。それがレコードBではなく、たまたまレコードAを更新したかった場合など、が今回のケースにあたります。 たしかに再帰であることは違いないのですが、フラットに分解できる再帰(「末尾再帰(tail recursion)」みたいなもの?)ですので、コールスタックが深くなってほしくないのです。
私が、もっとも疑問に思うのは、タイマーやスレッドのようなものを持ち出さないと、再帰が解決できないのだろうか?ということです。 換言すれば、私が悩んでいる再帰の問題は、言語レベルでは解決できず、タイマーやスレッドといった環境がないと解決できないものなのだろうか?ということです。 | ||||||||||||
|
投稿日時: 2008-04-08 07:11
イベント ハンドラの中で、同じイベントを発生させる、という状況が理解できません。
「阿波おどり」というイベントを実行中に「阿波おどり」というイベントを新たに実行することはできないと思います。すでに実行中なので。 オブザーバーは、互いに監視しあっているのですか?それなら再起もありえるけど、相互監視が必要な状況って? | ||||||||||||
|
投稿日時: 2008-04-08 09:19
相互に値を変更しあうことが避けられないとして、
それぞれの値変更時に「値が等しかったら何もしない」というような制御は行っていますか? 同じプロパティ(フィールド?)を無限に変更し続けることはなくなると思います。 よくあるプロパティ実装 public int Value { get { return m_Value; } set { -- 値が等しければ何もしない if ( m_Value == value ) { return; } m_Value = value; -- 値変更イベント OnValueChanged( EventArgs.Empty ); } } | ||||||||||||
|
投稿日時: 2008-04-08 09:33
レコードAがイベント発生を知っているわけですからなんで自分自身を変更するために Observerを用いる必要があるのですか? まず一番最初にあげられた例ではObserverを用いる必要がないのは確かですよね。 Hogeが自分を変更するときはHoge.foo()に定義すればいいわけですから 逆転させたい依存関係は観察ではなく実装のほうではないですか? 根本的な解決ではないと思いますが、 私はイベントの発生を抑止するときは下記のように書きます。
| ||||||||||||
|
投稿日時: 2008-04-08 10:09
私は、今、WebBrowser を制御して、いわゆる巡回アプリケーションを作るようなことをしているのですが、たとえば、つぎのようなことはできています。
すなわち、DocumentCompleted イベントハンドラーの中でページの最初のハイパーリンクに対して InvokeMember("click") をして、これは結局は新たに DocumentCompleted イベントを発生させることになります。これにより、ページがどんどん遷移します。 しかし、DocumentCompleted の中でブレークポイントで一時停止しても、コールスタックは深くなっていません。 WebBrowser のような挙動を、自分で作ったクラスでもやりたいのです。 |
1|2|3|4
次のページへ»