present@わんくま

本家はこちら

目次

Blog 利用状況

ニュース

アクセサリ

書庫

日記カテゴリ

ギャラリ

リンク

イベントを発生させるメソッドの実装

今まで、イベントを発生させるメソッドを何も考えずに

public event EventHandler<HelloEventArgs> Hello;

protected virtual void OnHello(HelloEventArgs e)
{
    if (Hello != null)
    {
        Hello(this, e);
    }
}

と書いていたけど、どうやらこれはマズイみたい。 スレッドセーフじゃない。

if (Hello != null)

を満たして

Hello(this, e);

に来るまでの間に、別のスレッドで

// クラス外部からイベントハンドラを解除するときは -= で行う。
// ↓ができるのはクラス内部だけ。
// Hello = null;

とされたら 全てのイベントハンドラが解除されたら NullReferenceException が発生してしまう。なので

public event EventHandler<HelloEventArgs> Hello;

protected virtual void OnHello(HelloEventArgs e)
{
    EventHandler<HelloEventArgs> tmp = Hello;
    if (tmp != null)
    {
        tmp(this, e);
    }
}

と書くのが望ましい。

これならと書けば、Hello がもともと null のときは if の条件を満たさないし、 別スレッドで Hello を null にされても tmp は null にならないので、 NullReferenceException は発生しない。

投稿日時 : 2008年10月23日 22:12

コメントを追加

# re: イベントを発生させるメソッドの実装 2008/10/24 1:20 渋木宏明(ひどり)

>別のスレッドで
>Hello = null;
>とされたら

されないです。
イベントハンドラの登録解除は null 代入じゃないです。

>と書くのが望ましい。

スレッドセーフにするなら、lock とかで保護しないとダメです。

けど、標準クラスライブラリに収録されているクラスでも、BackgroundWorker みたいな「そうする必要のあるもの」以外はスレッドセーフになってないと思いますよ。

# re: イベントを発生させるメソッドの実装 2008/10/24 8:27 なかむら

>イベントハンドラの登録解除は null 代入じゃないです。

クラスの外部からイベントハンドラを解除するときは -= ですね。
でもクラス内部でイベントハンドラを全解除するときは null 代入で出来ていたような…。
私の勘違いなんでしょうか?

# re: イベントを発生させるメソッドの実装 2008/10/24 8:58 なかむら

クラス内部では null 代入で全てのイベントハンドラを登録解除できました…が、こんなことをする場面ってほとんど無さそうですね。
全イベントハンドラの登録解除を行うメソッドが必要な時くらいでしょうか…。

あと文章の書き方も悪かったですね。投稿前に何度も見直しているんですが、それでも気付かないなんてorz

一部訂正しました。

# re: イベントを発生させるメソッドの実装 2008/10/24 9:20 渋木宏明(ひどり)

>イベントハンドラを全解除

意図的な全解除ですか。
それってめったにあるこっちゃないような (^^;

あと、スレッドセーフの件も見直した方がいいと思いますよ。
ちょっと修正しただけで、見た目の振る舞いがガラット変わるだけで、根本的には変わらないこともあるわけで。

今回の場合

>EventHandler<HelloEventArgs> tmp = Hello;
>if (tmp != null)

の区間を乗り越えたとしても

>if (tmp != null)
>{
> tmp(this, e);

の tmp(this, e) が実行されるまでの間に、別スレッドでイベントが登録解除(null 代入に限らず、-= によって最後のイベント登録が解除された場合とか)が行われれば、やっぱりクラッシュする可能性があるんじゃないかと。

# re: イベントを発生させるメソッドの実装 2008/10/24 11:44 なかむら

>意図的な全解除ですか。
>それってめったにあるこっちゃないような (^^;

確かに、めったにありません。
私は過去に1回だけ、そうせざるを得ない場面がありましたが(^^;


>スレッドセーフにするなら、lock とかで保護しないとダメです。

lock で保護ですか。う~ん、何で lock したらいいものか…。

protected virtual void OnHello(HelloEventArgs e)
{
EventHandler<HelloEventArgs> tmp = Hello;
if (tmp != null)
{
lock (tmp)
{
tmp(this, e);
}
}
}

としても、Hello イベントを外部からロックすることはできないので、
イベントハンドラを登録解除できてしまいますよね。

かといって、ロック用のオブジェクトを使って、

public readonly object LockObject = new object();
protected virtual void OnHello(HelloEventArgs e)
{
lock (this.LockObject)
{
EventHandler<HelloEventArgs> tmp = Hello;
if (tmp != null)
{
tmp(this, e);
}
}
}

とした場合、外からイベントハンドラを登録/解除するときに
利用側で lock してもらう必要があり、不親切ですね。

う~ん…。

# re: イベントを発生させるメソッドの実装 2008/10/24 11:55 なかむら

あっ!

デリゲートとイベントアクセサを自分で記述し、
クラス内部でデリゲートを lock してやれば上手くいきそう。

でもそれを全てのイベントでやるのは大変だ。
かといって他に上手い方法は見つかりません…。

# re: イベントを発生させるメソッドの実装(2) 2008/11/25 11:49 Nakamura Blog

re: イベントを発生させるメソッドの実装(2)

タイトル
名前
URL
コメント