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版」に記載されている。参照の事。