タイマーは5種類

非同期

はじめに

一定の間隔を置いて処理を実行させるためにはタイマーを使います

C#の標準ライブラリには5種類(4種類)のタイマーがあります。

その前に

実行スレッド

一定間隔毎に実行される処理をハンドラ内に定義します。これがどのスレッドで実行されるかはタイマー及びその設定に依存します
GUIのコンポーネントはUIスレッド以外から操作ができないので、非UIスレッドで実行させる場合はディスパッチする必要があります

一定間隔とは

実行間隔の指定は、その間隔毎に実行されることではなく、その指定間隔以上の間をあけて実行されることを意味します
つまり、1分間隔と指定した場合、ハンドラが再度実行されるのは丁度一分後という保証はないが、一分以内に実行されることはないことは保証されます
(公式文章の機械翻訳は誤訳ですが原文の英語を読むとよくわかります)

それタイマー?

例えば、30分毎に実行する必要がある処理の場合、それはタイマー起動ではなく、もしかしたらスケジューラで起動する方が正解かもしれません

タイマーの種類

System.Windows.Forms.Timer

・名前空間にあるようにFormを持つUI層での使用が想定される
・TickイベントハンドラはUIスレッドで実行される

System.Windows.Forms.Timer

System.Threading.Timer

・Threadingの名前空間にあるようにハンドラは(ほぼ)別スレッドで実行される
System.Threading.Timer

System.Timers.Timer

・サーバータイマーとも呼ばれる。一番正確とか一番重いとか言われるが、実測するとそうでもない感じ
・SynchronizingObjectを指定すればハンドラがUIスレッドで実行される。指定ないと別スレッド

System.Timers.Timer

System.Windows.Threading.DispatcherTimer

WPFで使用されることが想定される
・ハンドラはUIスレッドで実行される

System.Windows.Threading.DispatcherTimer

System.Web.UI.Timer

.NET Coreではつかえません

System.Web.UI.Timer(.net 4.8)

サンプルコード

・WinFormsのプロジェクトで画面にボタンを並べて作成したもののコードビハインド側
・DispatcherTimerはWPFプロジェクトで試行したものを合わせて記載してます
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
 
            Debug.WriteLine("Current UI Thread {0}", Thread.CurrentThread.ManagedThreadId);
 
            // Form
            var formTimer = new System.Windows.Forms.Timer();
            formTimer.Interval = 1000;
            formTimer.Tick += (_, __) => Debug.WriteLine("Forms.Timer {0}", Thread.CurrentThread.ManagedThreadId);
 
            buttonFormStart.Click += (_, __) => formTimer.Start();
            buttonFormStop.Click += (_, __) => formTimer.Stop();
 
            // Thead
            var theadTimer = new System.Threading.Timer((_) => Debug.WriteLine("Threading.Timer {0}", Thread.CurrentThread.ManagedThreadId));
 
            buttonThreadingStart.Click += (_, __) => theadTimer.Change(0, 1000);
            buttonThreadingStop.Click += (_, __) => theadTimer.Change(Timeout.Infinite, Timeout.Infinite);
 
            // Server 
            var serverTimer = new System.Timers.Timer();
            serverTimer.Interval = 1000;
            serverTimer.Elapsed += (_, __) => Debug.WriteLine("Timers.Timer {0}", Thread.CurrentThread.ManagedThreadId);
 
            buttonServerStart.Click += (_, __) => serverTimer.Start();
            buttonServerStop.Click += (_, __) => serverTimer.Stop();
 
            // Server(Sync)
            var serverTimer_Sync = new System.Timers.Timer();
            serverTimer_Sync.Interval = 1000;
            serverTimer_Sync.SynchronizingObject = this; // この設定があるとUIスレッドで実行される
            serverTimer_Sync.Elapsed += (_, __) => Debug.WriteLine("Timers.Timer (Sync) {0}", Thread.CurrentThread.ManagedThreadId);
 
 
            buttonServerStartSync.Click += (_, __) => serverTimer_Sync.Start();
            buttonServerStopSync.Click += (_, __) => serverTimer_Sync.Stop();
 
            // Dispatcher
            var dispatcherTimer = new DispatcherTimer(); 
            dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
            dispatcherTimer.Tick += (_, __) => Debug.WriteLine("Dispatcher.Timer {0}", Thread.CurrentThread.ManagedThreadId);
 
            buttonDispatcherStart.Click += (_, __) => dispatcherTimer.Start();
            buttonDispatcherStop.Click += (_, __) => dispatcherTimer.Stop();
        }
    }
}

どのタイマーを使うのか

WinFormsはFormのタイマー、WPFはDiscatherTimerでよいでしょう。
.NET FrameworkならWinFormsでもWindowsBase.dllへ参照を張ってDiscatherTimerを使う手もあります。
サーバーは基本System.Timers.Timerを使うが、場合に寄りSystem.Threading.Timerでしょうか。

使い分けに関しては以下のページが参考になります
Comparing the Timer Classes in the .NET Framework Class Library
非同期
スポンサーリンク