C# でタイマーを使う場合、System.Timers.Timer を使用することがよくあります。しかし、Stop メソッドを呼び出したにもかかわらず、タイミングによってはイベント (Elapsed) が発火してしまう現象に遭遇することがあります。この記事では、その理由と解決方法を解説します。
なぜStopしてもイベントが発火するのか?
System.Timers.Timer は内部的にスレッドプールを使用して Elapsed イベントを発生させます。
タイマーが停止しても、スレッドプールに既にスケジュールされた Elapsed イベントは実行されます。
何が言いたいかと言うと、タイミングによってはStopしてもイベント発火するようです。かなしみ。
発生例
以下のコードでは、Stop を呼び出してもイベントが発火してしまう場合があります。
qiita.C#
using System;
using System.Timers;
class Program
{
private static Timer _timer;
static void Main()
{
_timer = new Timer(1000); // 1秒間隔
_timer.Elapsed += TimerElapsed;
_timer.Start();
Console.WriteLine("Timer started. Press Enter to stop.");
Console.ReadLine();
_timer.Stop();
Console.WriteLine("Timer stopped.");
}
private static void TimerElapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Timer elapsed.");
}
}
対策方法
Elapsed イベントの内部で、タイマーが有効かどうかを確認するフラグを使用します。
qiita.C#
private static Timer _timer;
private static bool _isRunning = true;
static void Main()
{
_timer = new Timer(1000);
_timer.Elapsed += TimerElapsed;
_timer.Start();
Console.WriteLine("Timer started. Press Enter to stop.");
Console.ReadLine();
_isRunning = false; // イベントの実行を無効化
_timer.Stop();
Console.WriteLine("Timer stopped.");
}
private static void TimerElapsed(object sender, ElapsedEventArgs e)
{
if (!_isRunning)
{
return; // イベントをスキップ
}
Console.WriteLine("Timer elapsed.");
}
結論
System.Timers.Timer を使用する場合、Stop を呼び出してもイベントが発火する可能性があります。確実に防ぎたい場合は、フラグ管理や Dispose の併用や、DispatcherTimer を使用することがいいと思います。なぜなら、UI スレッド上で動作しているため、Stop を呼び出すだけで発火を確実に停止できます。