2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# バックグラウンドで常駐するクラスを作ってみた

Posted at

はじめに

自分用に System.Threading.Timer をラップして、バックグラウンドで常駐するワーカークラスを作ってみました。
フレームワークは Microsoft .NET 9.0 です。

ソース

コンストラクタでタイマーを開始し、キャンセルトークンで停止要求を受信します。
停止処理が実際に行われたかどうかは、Disposed プロパティで確認できます。

ThreadTimer.cs
using Timer = System.Threading.Timer;

namespace TestApp
{
    public class ThreadTimer
    {
        private readonly ILogger _logger;
        private readonly TimeSpan _interval;
        private readonly Action _callbackAction;
        private readonly CancellationToken _token;
        private readonly ManualResetEventSlim _disposedEvent;
        private readonly Timer _timer;

        public WaitHandle Disposed => _disposedEvent.WaitHandle; // 破棄完了の確認用

        public ThreadTimer(
            ILogger logger,
            TimeSpan interval,
            Action callbackAction,
            CancellationToken token)
        {
            _logger = logger;
            _interval = interval;
            _callbackAction = callbackAction;
            _token = token;
            _disposedEvent = new(false);

            // 0.1秒待ってタイマーを開始する
            TimeSpan dueTime = TimeSpan.FromMilliseconds(100);
            _timer = new Timer(TimerCallback, null, dueTime, _interval);
        }

        private void TimerCallback(object? state)
        {
            try
            {
                // 再入防止のため、タイマーを停止する
                _timer.Change(Timeout.Infinite, Timeout.Infinite);

                // キャンセルのリクエストがあった場合は
                // 処理を中断する
                if (_token.IsCancellationRequested)
                {
                    return;
                }

                // コールバック関数の実行
                _callbackAction();
            }
            catch (Exception ex)
            {
                _logger.Exception(ex);
            }
            finally
            {
                if (_token.IsCancellationRequested)
                {
                    // キャンセルのリクエストがあった場合は
                    // タイマーを破棄する
                    _timer.Dispose();

                    // 破棄完了を通知する
                    _disposedEvent.Set();
                }
                else
                {
                    // キャンセルされていない場合
                    // タイマーを再開する
                    _timer.Change(_interval, _interval);
                }
            }
        }
    }
}

使い方

WinForms での例です。
開始 / 終了ボタンでスレッドの生成と破棄を行います。

MainForm.cs

// 開始ボタンクリック
private void BtnStart_Click(object sender, EventArgs e)
{
    // 画面制御
    StartButton.Enabled = false;
    FooterStatus.Text = "開始準備中...";
    FooterProgressBar.Visible = true;
            
    // トークンの作成
    _cancellationTokenSource = new();
    _token = _cancellationTokenSource.Token;

    // スレッドの開始
    _timer1 = new(
        new Logger("_timer1"),
        TimeSpan.FromMilliseconds(1000),
        () => OnTestMethod1(),
        _token);

    _timer2 = new(
        new Logger("_timer2"),
        TimeSpan.FromMilliseconds(500),
        () => OnTestMethod2(),
        _token);

    _timer3 = new(
        new Logger("_timer3"),
        TimeSpan.FromMilliseconds(5000),
        () => OnTestMethod3(),
        _token);
        
    // 画面制御
    FooterProgressBar.Visible = false;
    FooterStatus.Text = "動作中";
    StopButton.Enabled = true;
}

// 停止ボタンクリック
private async void BtnStop_Click(object sender, EventArgs e)
{
    // 画面制御
    StopButton.Enabled = false;
    FooterStatus.Text = "停止準備中...";
    FooterProgressBar.Visible = true;

    // キャンセルの要請
    _cancellationTokenSource.Cancel();

    // タイマーの終了を待機する
    await Task.WhenAll(
        Task.Run(() => { _timer1.Disposed.WaitOne(); }),
        Task.Run(() => { _timer2.Disposed.WaitOne(); }),
        Task.Run(() => { _timer3.Disposed.WaitOne(); })
    );

    // 画面制御
    FooterProgressBar.Visible = false;
    FooterStatus.Text = "停止完了";
    StartButton.Enabled = true;
}

おわりに

お仕事では bool と lock を使ってゴリゴリに制御しているソースを見ることが多いので、今回 CancellationToken や ManualResetEvent を勉強できてよかったです。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?