投稿数 - 437, コメント - 55186, トラックバック - 156

変数のスコープは必ずしもブロック全体ではない

http://bbs.wankuma.com/index.cgi?mode=al2&namber=3529より

class Program
{
    static int count = 0;
 
    static void Main(string[] args)
    {
        System.Threading.Timer timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(TimerMethod), null, 100, 100); -- (1)
  
        while (true)
        {
            if (count > 10)
            {
                GC.Collect();
            }
 
            System.Threading.Thread.Sleep(1000);
        }
    }
 
    static void TimerMethod(Object state)
    {
        Console.WriteLine(count++);
    }
}

上記コードを Visual Studio 2005 で Release ビルドする。私のマシンでは「17」までカウントして止まってしまった。しかし、Debug ビルドバージョンだと永久にカウントし続ける。一体何が起こっているのか?

最適化というマジックにかかってしまうと「変数のスコープはブロックを抜けるまで」という常識が通じなくなってしまう。

上記コードの (1) 以後は、Main メソッドの中で timer 変数が使用されていない。という事は timer が参照しているインスタンスは GC の対象にしてもよい、と CLR に判断される。だから、永久にカウントされるはずが、途中でタイマーが破棄されて、TimerMethod が呼ばれなくなるという現象が発生する。

厄介なのが、最適化していなかったりデバッガをアタッチしている状態では、無理やり timer の寿命をブロック末尾まで伸ばす(多分 Jit Compiler がそのようにコンパイルする)。だから開発中はうまく動いているように見えてしまい、リリースしようとした瞬間、バグに気付くというわけ。

対処方法として一番良いのは、Timer のインスタンスをクラスのメンバなどにしてスコープを広げてあげる事。次点は、Timer のインスタンスをブロックの最後で触ってあげる事(上記の例では timer.Dispose() するか、using で囲めば良い)。但し、次点の方法では他のプログラマが意図に気付かないかも。まぁ、普通は IDisposable は Dispose() するものだが。

詳しい情報は「プログラミング .NET Framework 第2版」に記載されている。参照の事。

投稿日時 : 2007年5月15日 1:48

フィードバック

# re: 変数のスコープは必ずしもブロック全体ではない

こういうときの為に GC.KeepAlive メソッド があるのかな?と思っていました。でも IDisposable インタフェースを実装しているのなら、Dispose メソッドを呼ぶほうがいいですね。
2007/05/15 8:56 | ダッチ

# re: 変数のスコープは必ずしもブロック全体ではない

>こういうときの為に GC.KeepAlive メソッド があるのかな?と思っていました。

あ、それアリかもしれませんね。
2007/05/15 12:17 | 囚人

# re: 変数のスコープは必ずしもブロック全体ではない

KeepAliveもよくわからんメソッドですけどね。
2007/05/15 14:45 | シャノン

# re: 変数のスコープは必ずしもブロック全体ではない

いくつか実験しました。
※実験のたびにコードを元に戻しています。
実験1:Sleep の後に "GC.KeepAlive(timer);" を入れる
結果1:最適化ONでも止まらない
→ 初めて効能を体感できました。ループ内じゃなく、finally に書いても同じです。
実験2:最適化ONで timer を using する
実験2:止まらない
→ 行儀が良いことはいいですね!
実験3:最適化OFFで "System.Threading.Timer timer =" をコメントにする
結果3:最適化ONと同じく途中で止まる
→ 納得です
実験4:最適化ONで "Console.WriteLine(count++)" の代わりに "System.Media.SystemSounds.Beep.Play()" に変更する
結果4:止まらない(音が鳴り続ける)
→ なんでだろ? 単純じゃないっすね...
実験5:最適化ONで finally で " { args = new string[] { timer.ToString() }; }" を実行する
結果5:止まらない
→ GC.KeepAlive はヘルプの通り、単に参照してるだけなんでしょうね。
2007/05/15 15:21 | 通りすがり

# re: 変数のスコープは必ずしもブロック全体ではない

>→ なんでだろ? 単純じゃないっすね...

timer のジェネレーションが変わってるとか?
2007/05/15 18:52 | 渋木宏明(ひどり)

# re: 変数のスコープは必ずしもブロック全体ではない

public static KeepAlive(Object object){}
というように、何もしていないだけだったりして。InternalCall なので真実は闇。

>→ なんでだろ? 単純じゃないっすね...

GJ!
なんでだろ…。
2007/05/16 15:07 | 囚人

# re: 変数のスコープは必ずしもブロック全体ではない

ほめられました~
でも気付いてしまいました。
count++ しないと GC.Collect されないですね...orz
ごめんなさい (>_<)
2007/05/16 16:22 | 通りすがり

# re: 変数のスコープは必ずしもブロック全体ではない

>count++ しないと GC.Collect されないですね...orz

し、しまった…。めっちゃ調べてしまった…^^;
2007/05/16 20:21 | 囚人

# Michael Kors Tote Bag

http://www.oakley-discount-stores.pw/ oakley sunglasses
http://www.okoutlet-sale.pw/ oakley sunglasses
http://www.oakley-stores.pw/ oakley sunglasses
http://www.okoutlet-discount.pw/ oakley sunglasses
http://www.okley-sunglasses.pw/ oakley sunglasses
http://www.eyesglasseshut-ok.pw/ oakley sunglasses
http://www.oakley-discount-store.pw/ oakley sunglasses
http://www.cheapokglasses.pw/ oakley sunglasses
http://www.oakleyonlinestore.pw/ oakley sunglasses
http://www.oakley-sunglassesonline.pw/ oakley sunglasses
http://www.oakleysunglassesstores.pw/ oakley sunglasses
http://www.oakleyoutlet-sales.pw/ oakley sunglasses
http://www.oakleyonline-sales.pw/ oakley sunglasses
http://www.oakleysunglassesoutlet.pw/ oakley sunglasses
http://www.oakley-store.pw/ oakley sunglasses
2014/12/30 17:03 | dexuanfnt

コメントの投稿

タイトル
名前
URL
コメント