.NET の Timer を考えよう(3)


TimersDemo に経過時間表示を追加する

さて、TimersDemoの出力するメッセージに Stopwatch クラスを使った経過時間表示を追加してみましょう。Stopwatch クラスは時間経過がいつでも確認できる名前どおりのクラスです。

using System.Diagnostics;

これを追加したうえで MainWindow クラスに、このように記述しました。

public partial class MainWindow : Window
{
	private Stopwatch stpw = new Stopwatch(); // 追加
	public MainWindow()
	{
		InitializeComponent();
		this.Dispatcher.Thread.Name = "UI THREAD";
		stpw.Restart(); // 追加
	}
	// 以下省略

このように Stopwatch stpw 宣言とインスタンスの初期化をおこなって、Restart() でリセットして走らせます。あまり時間を使うのはよろしくないので、Stopwatch.ElapsedMilliseconds あたりでミリ秒の値を long 値で得て書きだすようにしましょう。それがこれです。

void WinTimer_Tick(object sender, EventArgs e)
{
	this.WinTimerList.Add(string.Format("Tick Generated from {0} ElapsedMilliseconds={1}", Thread.CurrentThread.Name,stpw.ElapsedMilliseconds));
	} // 変更

さて、他のタイマーも同様に変更して実行してみましょう。

timer5

ちょうど3回目の表示の直後あたりに Sleep ボタンを押しました。これは、System.Windows.Forms.Timer のときの表示です。ちょうど5秒(5000ミリ秒)のブランクがありますので、その間はイベントハンドラも呼ばれていないということが分かります。

System.Timers.Timer も見てみましょう。

timer6

今度は、Sleep の間も時刻どおりに処理できているようですね。だいたい1000(ミリ秒)づつ増加しています。でもどうでしょうすこしズレてきてます。Stopwatch クラスの時刻計算がずれているのかもしれません。まだ何とも言えませんがそれは別の機会にしましょう。脱線しすぎてしまいますし。

で、System.Threading.Timer の結果はこれです。

timer7

そして最後が DispatcherTimer です。

timer8

このようにみてくると、タイマーは2種類に大別できそうです。

UIスレッド型タイマー:UIスレッドが別の処理で忙しい間は止まる

  • System.Windows.Forms.Timer
  • DispatcherTimer

独自スレッド型タイマー:UIスレッドが別の処理で忙しい間も動き続ける

  • System.Timers.Timer
  • System.Threading.Timer

ということですね。UIスレッド型タイマー、独自スレッド型タイマーという名称を勝手につけましたが、どうでしょうか。

タイマー間隔を調べてみる

最後に全部一気に動かして、Sleepボタンは押さずにしばらく放置し、タイマーの間隔を計算してみましょう。ListBoxには数値だけを書き出すように、次のようにしました。

void WinTimer_Tick(object sender, EventArgs e)
{
	this.WinTimerList.Add(stpw.ElapsedMilliseconds.ToString());
	//this.WinTimerList.Add(string.Format("Tick Generated from {0} ElapsedMilliseconds={1}", Thread.CurrentThread.Name,stpw.ElapsedMilliseconds));
}

41回分の出力を Excel に取り込んでタイマー間隔を計算し、データ分析の「統計基本量」を使って計算した結果がこの表です。

timer10-table

System.Threading.Timer だけは0.1秒間隔、他は1秒間隔になっていますので比較するときには気をつけましょう。まず平均値です。本来1秒だと1000ですからやはりすこしズレているように見えますが、前述したようにこれは Stopwatch クラスの誤差かもしれないので、改めて確かめることにしましょう。最大値と最小値の差は「範囲」と呼びますが、System.Windows.Forms.Timer は36です。36ミリ秒しか揺れがないことにむしろ驚きです。このくらいですから、0.1秒(100ミリ秒)のタイマーでもそれほど正しくないということがいえそうです。一方、System.Timers.Timer、System.Threading.Timer(独自スレッド型タイマー) の範囲の値は 9 と 8 です。10ミリ秒以内に収まっています。3倍ほどよいということです。DispatcherTimer は System.Windows.Forms.Timer を少し改良したような値です。

まとめ

以上の結果から見ると、だいたい次のように結論付けられそうです。

  1. UIスレッド型タイマーの精度はせいぜい±20ミリ秒程度と考えておく
  2. より精度のよい(といってもせいぜい3倍)タイマーが必要なときは、独自スレッド型タイマーを使う
  3. UIスレッドが忙しい、Sleepなどで動作していない時にも動かしたいときは、独自スレッド型タイマーを使う
  4. UIスレッド型タイマーとしては、System.Windows.Forms.Timer よりも DispatcherTimer のほうがすこしキビキビしている

Timer12x600

今回、UIが忙しいとタイマーイベントは無視されているというのにはすこし驚きました。長い時間などを測るときや、タイマーイベントハンドラ内で数をカウントして、長めの時間間隔の処理をするといった時には注意が必要です。

いかがでしょう。Timerの使いわけ、すこしわかるようになりましたか?

余談ですが、ハードウェアの設計などをやっていた立場から見ると、タイマー処理時刻の精度は良くないですね。ハードウェアがらみですと、1ミリ秒程度の範囲でないと使い物になりません。今回は紹介しませんでしたが、DirectX 技術にマルチメディアタイマーというのもあります。それも試してみたいですね。

※ なお今回の測定は、Intel® Core™2 Duo E7400 2.8GHz の搭載された Windows 7 Professional のマシンでおこないました。当然ですが、もう少し早いマシンではいい結果が、遅いマシンでは悪い結果が出ると思われます。また、同時に動作している別のアプリケーションにCPUタイムをとられてさらに遅くなる可能性もあります。実用上の性能は、ここで述べた性能よりもさらに低いかもしれないと考えておくべきでしょう。

clock.NET の Timer を考えよう |3

印刷
You can skip to the end and leave a response. Pinging is currently not allowed.
Subscribe to RSS Feed Twitter は Ume108 だよ