System.Threading.Timer
クラスに関するドキュメントには、次のように記載されています。
Dispose() メソッドのオーバーロードが呼び出された後にコールバックが発生する可能性があることに注意してください。これは、タイマーがスレッド プール スレッドによる実行のためにコールバックをキューに入れるためです。
これは、Dispose()
メソッドを呼び出す前にスケジュールされたすべてのコールバックを完了する必要があることを意味します。しかし、コールバックはどのようにスケジュールされるのでしょうか? 次のコールバックはいつ予定されていますか?
PTimer
に等しい有限期間とDに等しい有限期限を持つクラスの新しいインスタンスを作成するとします。コールバックの最初の実行は、Dミリ秒が経過した後に発生します。後続のコールバックは、前のコールバックの開始からPミリ秒後に実行されます。コードサンプルを書くと、コールバックの実行時間が指定された期間Pよりも長い場合、タイマーがコールバックをキューに入れることがわかります。
コールバックがファイル上の一部のデータをバックアップする必要があるとします。コールバックの実行時間はバックアップの実行時間であり、ファイルに書き込む必要があるデータの量に依存するため、コールバックの実行時間は指定された期間を超える場合があります。この場合、現在のバックアップの最後に次のバックアップをスケジュールする方が適切です。この目的のためには、(期間を に設定して) 定期的な動作を無効にし、コールバックの最後にメソッドTimeout.Infinite
を呼び出すだけで十分です。Change()
次のサンプル コードのように。
// some private fields
private readonly object syncRootForTimer = new object();
private Timer backupTimer = new Timer(BackupCallback, null, 10000, Timeout.Infinite);
private bool isTimerDisposed = false;
// it performs timer shutdown
public void Shutdown()
{
WaitHandle disposed = AutoResetEvent(false);
lock(syncRootForTimer)
{
backupTimer.Dispose(disposed);
isTimerDisposed = true;
}
WaitHandle.WaitAll(new WaitHandle[]{ disposed });
// ...
}
private void BackupCallback(object state)
{
// It performs the backup procedure...
// ...
lock(syncRootForTimer)
{
if (!isTimerDisposed)
backupTimer.Change(10000, Timeout.Infinite);
}
}
このように、コールバックがキューに入れられていないようです。Dispose(WaitHandle)
また、最後のスケジュールの直後 (つまり、メソッドの最後の呼び出しの後Change()
) にメソッドを呼び出すと、この最後のスケジュールは実行されず、タイマーがすぐに破棄されることにも気付きました。
定期的な動作を無効にし、コールバック実行の最後に期限を変更すると、タイマーはコールバックをキューに入れないと言えると思います。Dispose()
これにより、メソッドが呼び出された後にコールバックが発生しないようにする必要があります。私の推測は正しいですか?