LoginSignup
7
5

More than 3 years have passed since last update.

C#, System.Timers.Timerクラスの安全な破棄方法

Last updated at Posted at 2020-12-14

目的

  • 定期的にバックグランドスレッドで何か処理するようなSystem.Timers.Timerを破棄するとき、破棄したタイミングでは絶対に処理が終わっていて欲しい、という要件を満たすラッパークラスを作る
    • TimerはStopやDisposeをしても、実行中処理が終わるのを待つわけではないので、スレッドが残っていることになります。
    • 特にユニットテストでは終了後にスレッドを残したくないです。スレッドが残っているとテストが不安定になることがあります。

コード

  • いきなりコードです。
  • 説明はコードコメントにて
  • もっといい方法がある、こんなもの必要無い、という方、返信お待ちしております。
using System;
using System.Timers;

namespace yourownnamespace
{
    /// <summary>
    /// 繰り返し処理専用のTimerラッパー
    /// Dispose後は実行中の処理が必ず無い状態となる
    /// 開始後は破棄のみ可能
    /// IDisposableのsnippestはほぼ、そのまま残してある
    /// </summary>
    internal class SafelyDisposableTimer : IDisposable
    {
        private bool disposedValue;
        // 定期的に実行したい処理
        private Action callbackAction;
        private Timer timer = new Timer();
        private object lockObject = new object();

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="interlval">インターバル(ミリ秒)</param>
        /// <param name="callbackAction">定期的に実行したい処理</param>
        internal SafelyDisposableTimer(int interlval, Action callbackAction)
        {
            this.callbackAction = callbackAction;
            this.timer.Elapsed += this.TimerElapsedEventHandler;
            this.timer.Interval = interlval;
            // 繰り返し処理する
            this.timer.AutoReset = true;
        }

        /// <summary>
        /// タイマーを開始する
        /// </summary>
        internal void Start()
        {
            this.timer.Start();
        }

        /// <summary>
        /// 破棄
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: マネージド状態を破棄します (マネージド オブジェクト)
                    this.timer.Stop();
                    this.timer.Dispose();
                }

                // TODO: アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします
                // TODO: 大きなフィールドを null に設定します
                lock (this.lockObject)
                {
                    disposedValue = true;
                }
            }
        }

        // // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
        // ~SafelyDisposableTimer()
        // {
        //     // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
        //     Dispose(disposing: false);
        // }

        /// <summary>
        /// 破棄
        /// </summary>
        public void Dispose()
        {
            // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        private void TimerElapsedEventHandler(object sender, ElapsedEventArgs args)
        {
            lock (this.lockObject)
            {
                if (this.disposedValue == true)
                {
                    return;
                }

                // 処理を実行
                this.callbackAction();
            }
        }
    }
}

7
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5