イベントの無限ループを回避する方法(詳細は中をご覧ください。)

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,664

Ryzna

score 60

毎度お世話になっております。Ryznaと申します。

タイトルに一言で表せなかったので改めて質問内容です。

環境:
 言語:C#
 .net framework:4.5.2
 実装したいところ:WCFサービスから参照されるDLL内

質問内容:
メソッドMを実行するとイベントEが発生するような実装のクラスAがあるとします。
クラスAから2つのインスタンス(それぞれインスタンス1,インスタンス2)を作成します。
インスタンス1.イベントEが発生した場合にインスタンス2.メソッドMを実行し、逆に、
インスタンス2.イベントEが発生した場合にインスタンス1.メソッドMを実行したいとします。
Eイベントから実行されたMメソッドが発生させるEイベントは処理したくないとします。

当然このままでは相互にイベントが発生し続けて無限ループになってしまうのでこれを回避するためにどのような実装をするべきなのでしょうか。

補足と現状での考え:
このプログラムはマルチスレッドで実行されており、当然別クラスからも呼ばれることも想定しておりますので、とりあえずロック内でイベントハンドラを一時的に回避するor「イベントを処理しない」フラグによる判定は思いついているのですがこれでいいのかなぁと自信がない次第です。

Aクラスは自前のクラスですので手を入れられない訳ではないのですが、既にリリースされているものであり出来れば手を入れずに済めばなぁと考えております。
そもそも根本の実装を修正しなきゃダメという情報でも構いませんので皆様のお知恵を拝借できればと思います。

よろしくお願いします。
以上

追記:
4/15 19:30 頃までにご提案頂いた方法の中からベストアンサーを決定させていただきます。
それ以降も継続してご意見ご提案の募集は継続させて頂きます。よろしくお願いします。

追記2:
4/15 20:00の時点でご提案いただいていた3つの方法のうち、
・既存コードに手を加えない
・一連のフローの中で切り替えが行える
・クラスAと異なる構造でも適用できる
の3つの点でtorakichiさんの案をベストアンサーとさせていただきました。
もっといい方法があるよ!というご提案は引き続き募集させていただきますのでぜひご投稿いただければと思います。

追記3:
以下サンプルです。
// 既存のクラス
// 既にリリース済み別案件で使用中なので出来れば手を入れたくない。入れちゃダメというわけでもない
public class ClassA
{
    public event EventHandler EventE;

    public void MethodM()
    {
        var e = new EventArgs();

        // MethodM内のいろいろ処理

        OnEventE(e);
    }

    protected virtual OnEventE(EventArgs e)
    {
        // OnEventE内のいろいろ処理

        if (EventE != null)
            EventE(this, e);
    }
}

// ものすごく簡略化した新規コード
public class Hoge
{
    private ClassA _instance1;
    private ClassA _instance2;

    public Hoge()
    {
        _instance1 = new ClassA();
        _instance2 = new ClassA();
    }

    private void Instance1_EventE(object obj, EventArgs e)
    {
        // インスタンス1.EventE発生時のいろいろ処理

        _instance2.MethodM(); // ここでは_isntance2.EventEが発生してほしくない
    }

    private void Instance2_EventE(object obj, EventArgs e)
    {
        // インスタンス2.EventE発生時のいろいろ処理

        _instance1.MethodM(); // ここでは_isntance1.EventEが発生してほしくない
    }

    public void MethodA()
    {
        // Hoge.MethodAとしていろいろ処理

        _instance1.MethodM(); // ここでは_isntance1.EventEが発生してほしい
    }

    public void MethodB()
    {
        // Hoge.MethodBとしていろいろ処理

        _instance2.MethodM(); // ここでは_isntance2.EventEが発生してほしい
    }
}

この状態のままMethodA,Bのどちらかを実行したら、当然相互にイベントが発生し続けてしまうわけで、それは困るということです。
現状はこのInstance1_EventE,Instance2_EventEの中でReaderWriterLock(実コードにてそういうロックが必要なのでそのまま流用しています。)のWriterでロックを掛けておいて、さらにその中でイベントハンドラの解除・追加を行うようにしていますが、それ以外に良い方法は無いかなぁと模索している状況です。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

0

はじめのイベントを発生させるタイミングは捕まえることができるのでしょうか?
それならば私なら一旦イベントを抜いてしまうと思います。
インスタンス1のイベントEが発生するタイミングで


インスタンス2.イベントE -=(イベント)
インスタンス1.イベントE
インスタンス2.イベントE +=(イベント)

といった具合でインスタンス1のイベントが終了したタイミングで
イベントを戻します。
ご期待の処理にかなっているかわかりませんが参考までに。



投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/15 20:12

    ご提案ありがとう御座います。

    まったく同じ事を考えてとりあえず実装したのがこの方法です。
    動きとしては今のところ問題もなく動いては居るのですが、「補足と現状での考え: 」にも書きましたようにこの方法で良いのかなぁ・・・もっといい方法ないかなぁと思い質問を投稿させていただきました。

    もし他の方法をご存知でいらっしゃればご教示頂ければ幸いです。

    キャンセル

0

「イベントEからメソッドMが呼び出されても処理しない」のではなく
「イベントEからメソッドMを呼び出さない」という実装でも構わないですか?

自分であれば、

クラスAを継承したクラスA'を作成
クラスA'にイベントEを発生させないメソッドM'を作成(イベントEを発生させないだけであとはコピー)
インスタンス1,インスタンス2をクラスA'で生成するように修正
各インスタンスのイベントEのハンドラを各メソッドM'に変更

という感じに実装しますかね。。。

やはりリリース後ということですのでフラグで分岐を増やしたり
既存のコードに修正が入ったりする実装は避けたいですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/14 18:09

    ご提案ありがとう御座います。

    > 「イベントEからメソッドMを呼び出さない」という実装でも構わないですか?
    条件としては問題ありませんが、

    > クラスA'にイベントEを発生させないメソッドM'を作成(イベントEを発生させないだけであとはコピー)
    については修正やら改変やらといったコード管理の点で避けたい手法ですね。。。

    >やはりリリース後ということですのでフラグで分岐を増やしたり
    >既存のコードに修正が入ったりする実装は避けたいですね。
    まったく持ってその通りです・・・OTL

    キャンセル

  • 2015/04/14 18:43

    >> クラスA'にイベントEを発生させないメソッドM'を作成(イベントEを発生させないだけであとはコピー)
    >については修正やら改変やらといったコード管理の点で避けたい手法ですね。。。
    クラスAのMのイベント以外の処理をリファクトしてイベントハンドラを
    イベント以外の処理にするのが妥当ですかね。
    この修正でロジックが変わる訳ではないですし。

    public class A{
    public void M(){
    this.M_nonEvent();
    E(); // Eイベント
    }

    public M_nonEvent(){
    // Mのイベント以外の処理
    }
    }

    ただ私はクラスA'の後に、さらに修正の必要が発生したらクラスA''とか作ってそうですw

    キャンセル

  • 2015/04/14 19:28

    なるほど。。。
    修正範囲が拡大しそうな気がするのでご提案頂いた方法は見送らせていただこうと思います。
    最初から今回のような用法を考慮したつくりに出来ればよかったんですがなかなかうまくいかないもんです。

    キャンセル

0

書いているうちにごちゃごちゃしてきて恥ずかしいことになってますが、伝わればいいなと思って投稿します。
イベントが既に発火しているか否かの状態を表す列挙体を定義し、それを内部変数に持つEventArgsの派生型を使うことで判断しようという試みです。
class Program
{
    static void Main(string[] args)
    {
        var instance1 = new ClassA();
        var instance2 = new ClassA();

        instance1.EventE += (sender, e) =>
        {
            if (e.Status == EventStatus.NotFired)
            {
                instance2.MethodM(EventStatus.Fired);
            }
        };
        instance2.EventE += (sender, e) =>
        {
            if (e.Status == EventStatus.NotFired)
            {
                instance1.MethodM(EventStatus.Fired);
            }
        };

        instance1.MethodM(EventStatus.NotFired);
    }
}

class ClassA
{
    public event EventHandler<MyEventArgs> EventE;
    protected virtual void OnEventE(MyEventArgs e)
    {
        if (this.EventE != null)
            this.EventE(this, e);
    }
    public void MethodM(EventStatus status)
    {
        // ここに行いたい処理を入れる

        if (status == EventStatus.NotFired)
            this.OnEventE(new MyEventArgs(EventStatus.NotFired));
    }
}

class MyEventArgs : EventArgs
{
    public EventStatus Status { get; private set; }

    public MyEventArgs(EventStatus status)
    {
        this.Status = status;
    }
}

enum EventStatus
{
    NotFired,
    Fired,
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/15 10:37

    ご提案ありがとう御座います。

    なるほど。
    イベントからの実行であるかメソッドパラメータで受け取り、そのままイベントパラメータに引き継いでハンドラ内で判断するということですね。
    メソッドパラメータに既定値としてNotFiredを設定しておけば既存のコードに手を入れなくてすみますね。既存コードでイベントパラメータのステータスを気にする必要は無いはずですし。

    「クラスA自体の修正」案として保留させていただきます。

    キャンセル

0

クラスAの作りって以下のような感じになってると考えていいんでしょうか?

public void MethodM()
{
    // ここで色々な処理… 仮に作業Aとする

    OnEventE(); // 別インスタンスのイベントに紐づいており、MethodM が呼び出される
}

であれば、メソッドMをリファクタリングし、「処理」と「イベント呼び出し」の二つに分けてはいかがでしょうか。

上でいう作業Aの部分をプライベート関数にし、OnEventの処理を切り離します。
private methodM();

MethodMの中ではmethodMとOnEvent関数を呼び出します。
public MethodM()
{
    methodM();
    OnEventE();
}

で、OnEventEではこのプライベートなmethodMの方を呼びます。
同じクラスの別インスタンスということなので、プライベートメソッドでも呼び出すことができます。
ちょっと違和感あるかもしれませんが…。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/16 11:12

    ご提案ありがとう御座います。

    > クラスAの作りって以下のような感じになってると考えていいんでしょうか?
    OnEventEは自インスタンスのイベントを発生させております。
    今回新規に作ったコード(まったく別のクラス)上でたまたま片方のイベント発生時に別インスタンスのメソッドMを実行する必要が出てきた状態です。

    > であれば、メソッドMをリファクタリングし、「処理」と「イベント呼び出し」の二つに分けてはいかがでしょうか。
    確かにこの二つに分離して処理のみ、処理+イベント発生を組み合わせられるようにする構造としておけば解決できるわけですよね。
    ただ、今回はClassA自身が別インスタンスを保持するわけではないのでご提案いただいているコードは実現できなさそうです。
    やるとしたらイベントを発生させる代わりにデリゲートで処理を受け取る形間でしか出来無そうですが、それをやると既存コードへの影響が甚大なので流用ではなくコピペで別クラスにせざるを得ないです;

    新たにイベントを持つクラスを設計する際にこの考え方を参考にさせていただきたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる