このカテゴリの記事一覧

2015年03月02日

タイマー 4種類

はじめに

一定の間隔で処理を実行するにはタイマーを使います。
標準ライブラリには4種類のタイマーが定義されています。

指定した間隔で実行されるイベントハンドラが実行されるのがUIスレッドなのかワーカスレッドなのかが重要。
UIコンポーネントと呼ばれる画面を構成するボタンやテキストボックスなどはUIスレッドでしか操作できないから。
ハンドラがUIスレッドで動くのなら、直接にボタンやテキストボックスを操作することができますが、UIスレッドが混雑しているとちゃんと動かない場合がありえる。
ハンドラがワーカスレッド(UIスレッドとは別のスレッド)の場合、重たい処理でも画面を固まらせずに実行されうるが、結果をUIコンポーネントに表示させる場合などはUIスレッド側にディスパッチしてやる必要がある。

System.Windows.Forms.Timer

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

System.Threading.Timer

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

System.Timers.Timer


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

System.Windows.Threading.DispatcherTimer

・WPFやSilverLightで使用されることが想定される。
・ハンドラはUIスレッドで実行される。
・WindowsFormsでも使える。WindowsBase.dllへの参照の設定が必要

サンプルコード

・WindowsFormsの画面にボタンを8つ並べて作りました。
・下記のコードはそのFormの画面のコードビハインド側です。
・DispatcherTimerを使うためにWindowsBase.dllの参照を明示的に設定しました。

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

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 System.Windows.Threading.DispatcherTimer(); // add reference to WindowsBase.dll
            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();
        }
    }
}

最後に

常駐型アプリケーションに組み込まれたタイマー処理を見ると、本当にタイマー処理で実装する必要があるのか疑問なものが多い。 「30分毎に実行」なんて仕様を見かけたら「タイマー」と思うまえに「タスク・スケジューラ?」と考えてみる。
posted by RR at 01:23 | Comment(0) | 非同期 | このブログの読者になる | 更新情報をチェックする