- PR -

イベントハンドラーの中でさらにイベントを起こすと、再帰的に呼び出されてしまう

投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-04-08 10:18
引用:

masaさんの書き込み (2008-04-08 09:19) より:
相互に値を変更しあうことが避けられないとして、
それぞれの値変更時に「値が等しかったら何もしない」というような制御は行っていますか?
同じプロパティ(フィールド?)を無限に変更し続けることはなくなると思います。


ありがとうございます。
「無限に変更し続けること」自体は、起こっても構いません。更新は何秒間隔でおこなう、や、ある条件に達したらやめる、などのような制御は埋め込みますので、CPU資源を消費しつくすなどのような問題は起こりません。
更新し続ける際にコールスタックが深くなるのだけを防ぎたいと思っています。
masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2008-04-08 10:19
"思いつき" ですが、

巡回処理をコマンドクラスとして定義して、
対象とする url を変更しながら繰り返すとかはどうでしょう。
yield を使ったりして。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-04-08 10:26
引用:

otfさんの書き込み (2008-04-08 09:33) より:
根本的な解決ではないと思いますが、
私はイベントの発生を抑止するときは下記のように書きます。
コード:
        private void hoge_HogeHandler(object sender, HogeEventArgs e)
        {
            hoge.HogeHandler -= hoge_HogeHandler;
            try
            {   
                Console.WriteLine("hoge_HogeHandler");
                hoge.foo();
            }
            finally
            {
                hoge.HogeHandler += hoge_HogeHandler;
            }
        }




みなさま、私の質問が分かりづらかったですね。すみません。
連鎖更新自体を防ぎたいのではないです。更新自体は連鎖してほしいのですが、その更新が、コールスタックを消費しておこなわれるのではなく、キューイングしてほしいのです。

ここまで書いてて気づきましたが、やっぱりキューを自分で作って使わないとダメなのでしょうか。それを備えているのが、たまたま種々の Timer うんちゃらクラスであったりするわけでしょうか。
otf
ベテラン
会議室デビュー日: 2006/08/04
投稿数: 91
投稿日時: 2008-04-08 12:03
要するに非同期処理を同期的に行いたいって事ですか?
その上で繰り返しを行う。

コード:
    public partial class Form1 : Form
    {
        private WebBrowser browser = new WebBrowser();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            while (true)
            {
                IAsyncResult result = browser.Navigate();
                result.AsyncWaitHandle.WaitOne();
                Console.WriteLine("hoge");
            }
        }
    }

    delegate void CompletedHandler();

    class WebBrowser
    {
        public event CompletedHandler Completed;

        public WebBrowser()
        {
        }

        public IAsyncResult Navigate()
        {
            ThreadStart h = NavigateWork;
            return h.BeginInvoke(OnCompleted, h);
        }

        public void NavigateWork()
        {
            Thread.Sleep(1000); // ナビゲート完了まで1秒くらいかかる。
        }

        protected virtual void OnCompleted(IAsyncResult ar)
        {
            ThreadStart h = ar.AsyncState as ThreadStart;
            h.EndInvoke(ar);
            if (Completed != null) Completed();
        }
    }



見当違いでしたらごめんなさい。
未記入
大ベテラン
会議室デビュー日: 2008/02/07
投稿数: 115
投稿日時: 2008-04-08 12:20
webBrowser1 でコールスタックが深くならないのは、GUIコンポーネントだからじゃないですかねえ。InvokeMember("click")ってウェブページ上のリンクをマウスでクリックするのと同じ行為なわけだから、当然 UIスレッドから実行されるべきものだと思います。

webBrowser1 は、UIスレッドにイベントを積んでるからコールスタックが深くならないのだと思います。コールスタックを深くしたくないのであれば、自前のスレッドにイベントキューを持たせるのがいいんじゃないでしょうか。私は、そのようなコードを書いています。

Java の例で申し訳ないですけど、paint() の中で repaint() 要求してもコールスタックが深くならないのも同じ理由ですよね。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2008-04-08 18:10
引用:

unibonさんの書き込み (2008-04-08 10:26) より:
連鎖更新自体を防ぎたいのではないです。更新自体は連鎖してほしいのですが、その更新が、コールスタックを消費しておこなわれるのではなく、キューイングしてほしいのです。



キューイングしてほしいならキューを使わなきゃだめなのは当然ですよね?
キューとイベントはまったく違う概念ですから。
もちろん密接に関連していますが。

Windowメッセージなどと混乱してるのではないでしょうか?
Win32なら、ユーザーメッセージを定義してPostMessageで投げてやれば、
メッセージキューがキューイングしてくれますから、
再帰にならずに連続してタスクを実行できます。

もちろん、.Netでもこの手は使えますが、
Frameworkのメッセージングとバッティングしたりする可能性があります。
私なら自前のキューを用意して、
Application.Idleでキューからタスクを取り出して処理をします。
WebBrowserの更新ならキューの大きさはひとつでいいので、
ただのフラグでOKですね。

System.Windows.FormsのイベントやWebBrowserのイベントは
Win32メッセージなど下位のコンポーネントを引きずっていますから、
純粋にイベントだけで駆動されてはいないので、
どのイベントがどのイベントを起こすのか、とか、
どこで処理がキューイングされてるのかとか、
わかりづらい面は大いにあると思います。

indigo-x
大ベテラン
会議室デビュー日: 2008/02/21
投稿数: 207
お住まい・勤務地: 太陽の塔近く
投稿日時: 2008-04-08 21:41
すでに出ていますが、(Windows系)Control.Invoke(or MethodInvoker)の
(非同期)を使用すればMainスレッドにキューしたのと同じ動作になったはず
(ひょっとしたら同一スレッドの場合はならないかも?知れませんが)
これをうまく使うのも手と思います。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-04-11 18:50
いろいろな回答ありがとうございます。
拝見していて Invoke 系のやつを使うのが良さそうだと思うのですが、ちょっとまだ理解できていません。

とりあえず、ぜんぜん別のやりかたですが、つぎのようなやりかたもあることに気づきましたのでひとまず書いておきます。イベント引数で制御するやりかたであり、たとえていえば、自分でメッセージループを作るような感じです。再帰処理をループに展開するような感じ?
コード:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace HogeApp
{
public partial class Form1 : Form
{
private Hoge hoge = new Hoge();

public Form1()
{
InitializeComponent();
hoge.HogeHandler += new HogeEventHandler(hoge_HogeHandler);
}

private void hoge_HogeHandler(object sender, HogeEventArgs e)
{
Console.WriteLine("hoge_HogeHandler");
//hoge.foo();
e.Call = true;
}

private void button1_Click(object sender, EventArgs e)
{
hoge.foo();
}
}

class HogeEventArgs : EventArgs
{
private Nullable<bool> call;

public HogeEventArgs()
{
call = null;
}

public HogeEventArgs(bool argCall)
{
call = argCall;
}

public Nullable<bool> Call
{
get
{
return call;
}
set
{
call = value;
}
}
}

delegate void HogeEventHandler(object sender, HogeEventArgs e);

class Hoge
{
public event HogeEventHandler HogeHandler;

public Hoge()
{
}

public void foo()
{
while (true)
{
if (HogeHandler != null)
{
HogeEventArgs hea = new HogeEventArgs();
HogeHandler(this, hea);
if (!hea.Call.Value)
{
break;
}
}
}
}
}
}



(あとで無駄なコードがあったので削除しました。)

[ メッセージ編集済み 編集者: unibon 編集日時 2008-04-11 19:49 ]

スキルアップ/キャリアアップ(JOB@IT)