このカテゴリの記事一覧

2015年03月02日

タイマー 4種類

はじめに

一定の間隔を置いて処理を実行させるためには、タイマーを使います。
標準ライブラリにはタイマーが4つあります。

その前に
ハンドラはどのスレッドで実行されるのか
一定間隔毎に実行される処理をハンドラに定義します。この処理がどのスレッドで実行されるのかはタイマー及びその設定に依存します。 タイマーを定義したスレッド(通常はメインスレッドとなるUIスレッド)で実行される場合は、UIコンポーネントを操作できますが、重い処理などを定義されているとUIスレッドが重くて画面が固まって反応しない副作用が発生する可能性があります。
ワーカスレッド(UIスレッド以外のスレッド)で実行される場合は、その処理中もUIが固まることはありませんが、そのスレッドからUIコンポーネントを操作することが出来ません。

指定間隔毎に実行されることは保証されていない
MSDNの邦訳が意味不明ですが原文の英語(簡単です)を読むと分かります。曰く、インターバルの指定はその間隔毎に実行されることではなくて、その間隔以上を開けて実行されることを意味します。つまり、例えば、1分毎と指定した場合、ハンドラの実行後に次回のハンドラが1分以内に実行されることはないという意味になります。

タイマーを使う必要あるのか
例えば30分毎に実行する必要のあるプログラムを組む場合に、インターバルを30分としていたタイマーを使うのではなく、30分毎に起動するようスケジューラに設定するのが正解の場合もあります。必ずしもタイマーを使うことが正解とは限らない。

タイマーの種類

System.Windows.Forms.Timer

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

System.Threading.Timer

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

System.Timers.Timer


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

System.Windows.Threading.DispatcherTimer

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

サンプルコード

・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();
        }
    }
}
posted by RR at 01:23 | Comment(0) | 非同期 | このブログの読者になる | 更新情報をチェックする