タイマにより一定時間間隔で処理を行うには?(サーバベースタイマ編):.NET TIPS
System.Timers名前空間で提供されているTimerクラスを利用して、一定間隔で処理を実行する方法を解説する。
本稿は2005/11/11に初版公開された記事を改訂し、Visual Studio 2017でコードの動作検証、ラムダ式の使用例、WPFアプリでの使用例、図版の追加、全般的な構成の変更などを行ったものです。
.NET Frameworkには一定時間間隔で処理を行う(メソッドを呼び出す)ためのタイマ機能として、以下の3種類のTimerクラスが用意されている。
本稿では3のサーバベースタイマについて、その基本的な使い方をまとめる。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
サーバベースタイマ:System.Timers.Timerクラスの基本
System.Timers名前空間のTimerクラスはサーバベースタイマとも呼ばれ、Windowsアプリだけでなくサーバサイドアプリでの利用も想定されており、より正確な時間間隔でメソッドを実行できる。
このTimerクラスでは、ElapsedEventHandlerデリゲート(System.Timers名前空間)を使用して、タイマにより呼び出されるメソッド(以下、タイマメソッドと記す)のデリゲートを作成し、TimerクラスのElapsedイベント(elapseは「時が経過する」の意味)に登録する(VB.NETではWithEvents/Handlesキーワードによりイベントを登録することも可能)。
タイマメソッドの呼び出し間隔は、コンストラクタのパラメーターかIntervalプロパティで指定できる(単位はミリ秒)。AutoResetプロパティでは、タイマメソッドを継続して呼び出すか(trueに設定。デフォルト値)、1度だけ呼び出すか(falseに設定)を指定する。また、タイマの開始/停止は、Enabledプロパティにtrue/falseを設定して行う(Start/Stopメソッドを呼び出してもよい)。
以下にサーバベースタイマを利用したサンプルプログラムを示す。MyClockメソッドがタイマにより一定間隔で実行されるメソッドである。Visual StudioでVBのコンソールアプリプロジェクトを新規に作成して、以下のコードを試す場合には、ソリューションエクスプローラーの[My Project]をダブルクリックして、[アプリケーション]タブにある[スタートアップ オブジェクト]に[Sub Main]か[TimersTimerTest]に変更する必要がある。
// timerstimer.cs
using System;
using System.Timers;
public class TimersTimerTest {
static void Main() {
TimersTimerTest ttt = new TimersTimerTest();
ttt.Run();
}
public void Run() {
Timer timer = new Timer();
timer.Elapsed += new ElapsedEventHandler(MyClock);
timer.Interval = 1000; // コンストラクタでも指定可
timer.AutoReset = true; // デフォルト
timer.Enabled = true; // timer.Start()と同じ
Console.ReadLine(); // Enterキーが押されるまで待機
timer.Enabled = false; // timer.Stop()と同じ
Console.WriteLine("タイマ停止");
timer.Dispose();
}
public void MyClock(object sender, ElapsedEventArgs e) {
Console.WriteLine(DateTime.Now);
// 出力例:
// 2005/11/08 19:59:10
// 2005/11/08 19:59:11
// 2005/11/08 19:59:12
// ……
}
}
// コンパイル方法:csc timerstimer.cs
' timerstimer.vb
Imports System
Imports System.Timers
Public Class TimersTimerTest
Shared Sub Main()
Dim ttt As TimersTimerTest = New TimersTimerTest()
ttt.Run()
End Sub
Public Sub Run()
Dim timer As Timer = New Timer()
AddHandler timer.Elapsed, _
New ElapsedEventHandler(AddressOf MyClock)
timer.Interval = 1000 ' コンストラクタでも指定可
timer.AutoReset = true ' デフォルト
timer.Enabled = true ' timer.Start()と同じ
Console.ReadLine() ' Enterキーが押されるまで待機
timer.Enabled = False ' timer.Stop()と同じ
Console.WriteLine("タイマ停止")
End Sub
Public Sub MyClock(sender As Object, e As ElapsedEventArgs)
Console.WriteLine(DateTime.Now)
' 出力例:
' 2005/11/08 19:59:10
' 2005/11/08 19:59:11
' 2005/11/08 19:59:12
' ……
End Sub
End Class
' コンパイル方法:vbc timerstimer.vb
サーバベースタイマはWindowsフォーム用のTimerコンポーネントと異なり、Webフォームなどでも利用可能だ。ちなみにこのタイマは、Windows OSの「待機可能タイマ」と呼ばれるタイマをベースにしている(Win32 APIではCreateWaitableTimer関数により待機可能タイマを作成できる)。
なお、タイマメソッドを継続して呼び出す場合、呼び出し間隔よりもタイマメソッドの処理にかかる時間が長くなっても、タイマメソッドは呼び出される。タイマメソッドの処理中に重複してタイマメソッドが呼び出されることになるので、注意してほしい。そのような可能性があるときは、タイマメソッドをリエントラント(再入可能)に作る必要がある。
ラムダ式で簡潔に記述する
.NET Framework 3.5からは、デリゲートに替えてラムダ式が使える。タイマメソッドの内容がさほど長くないときは、ラムダ式にすると簡潔に記述できる。上のコードをラムダ式を使って書き直すと、次のコードのようになる。
using System;
using System.Timers;
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer(1000); // コンストラクタでIntervalを指定
// ElapsedEventHandlerをラムダ式で定義
timer.Elapsed += (s, e) => Console.WriteLine(DateTime.Now);
timer.Start();
Console.ReadKey(); // キーが押されるまで待機
timer.Stop();
Console.WriteLine("タイマ停止");
timer.Dispose();
}
}
Imports System.Timers
Module Module1
Sub Main()
Dim timer As Timer = New Timer(1000) ' コンストラクタでIntervalを指定
' ElapsedEventHandlerをラムダ式で定義
AddHandler timer.Elapsed, Sub(s, e) Console.WriteLine(DateTime.Now)
timer.Start()
Console.ReadKey() ' キーが押されるまで待機
timer.Stop()
Console.WriteLine("タイマ停止")
timer.Dispose()
End Sub
End Module
この例と前の例では、明示的にDisposeメソッドを呼び出す代わりにusing句を使ってもよい。実際には、後に示すWPFの例のように、Timerクラスのインスタンス化と破棄は別の場所になることが多い(=using句が使えない)。
Windowsフォームでの利用について
Visual Studioでプログラミングしている場合には、ツールボックスの[コンポーネント]タブから「Timerコンポーネント」をフォームにドラッグ&ドロップするだけで利用可能な状態となる。配置したTimerコンポーネントをダブルクリックすれば、タイマメソッド(Elapsedイベントハンドラ)が自動的に作成される。なお、ツールボックスに[Timer]が1つだけの場合、それはWindowsタイマ(System.Windows.Forms名前空間)なので、[ツールボックス アイテムの選択]ダイアログでSystem.Timers名前空間のTimerを追加して使用する。
このタイマでは、スレッドタイマ(System.Threading.Timeクラス)と同様に、.NET Frameworkが管理するスレッドプールによりタイマメソッドが実行されるため、タイマメソッドはTimerクラスをインスタンス化したスレッドとは異なるスレッドで実行される。このため、これをWindowsフォームで利用する場合にはコントロールの操作に関して注意が必要となる(これについては「TIPS:Windowsフォームで別スレッドからコントロールを操作するには?を参照」)。
しかしサーバベースタイマにはSynchronizingObjectプロパティが用意されており、このプロパティにフォームのインスタンスを設定することにより、タイマメソッドがUIスレッドで実行されるようになる。
ただしこの場合には、もしUIスレッドで時間のかかる処理を行っていると、タイマメソッドの呼び出しが待たされることになる(SynchronizingObjectプロパティを利用した場合の挙動については「TIPS:Windowsアプリケーションでファイルやディレクトリを監視するには?」の後半部分が参考になる)。
なお、Visual Studio .NETでTimerコンポーネントをWindowsフォームに配置した場合には、自動的にWindowsフォームのインスタンスがSynchronizingObjectプロパティに設定される。
WPFでの利用について
WPFでは、Windowsフォームと同様にSynchronizingObjectプロパティを利用してもよいが、Dispatcherクラス(System.Windows.Threading名前空間)を使って別スレッドからUIを操作するのが一般的だ。
Dispatcherクラスの使い方は、「タイマにより一定時間間隔で処理を行うには?(スレッドタイマ編)」で詳しく説明してある。ここでは、WPFアプリの簡単な例を紹介しよう。
まず、画面には次のコードのようにして、TextBlockコントロールを配置しておく。
<Grid>
<Viewbox>
<TextBlock x:Name="TextBlock1"
FontFamily="Harlow Solid Italic" FontSize="36"
TextAlignment="Center" Margin="5,0,10,0"
Text="00:00:00" FontWeight="Bold" >
<TextBlock.Foreground>
<SolidColorBrush
Color="{DynamicResource {x:Static SystemColors.HighlightColorKey}}"/>
</TextBlock.Foreground>
</TextBlock>
</Viewbox>
</Grid>
コードビハインドは次のコードのようになる。InitializeComponentメソッド呼び出しの後で、SetupTimerメソッドを呼び出している。そのSetupTimerメソッドでは、タイマの初期化と開始を行い、さらにウィンドウが閉じられるときにタイマを破棄するようにイベントを設定している。タイマから呼び出されるイベントハンドラはラムダ式で定義してあり、画面が持っているDispatcherインスタンスを使ってデリゲートを実行している。そのデリゲートも、ラムダ式で記述したActionデリゲートになっている。
using System;
using System.Timers;
using System.Windows;
namespace dotNetTips0374WpfCS
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SetupTimer();
}
private Timer _timer;
private void SetupTimer()
{
// タイマの初期化と開始
_timer = new Timer(1000);
// ElapsedEventHandlerをラムダ式で定義
_timer.Elapsed += (s, e) => {
// WPFではDispatcherを使ってUIスレッドでの処理を実行する
this.Dispatcher.Invoke(new Action(() => {
this.TextBlock1.Text = DateTime.Now.ToString("HH:mm:ss");
}));
};
_timer.Start();
this.Closing += (s, e) => {
// 画面が閉じられるときに、タイマを停止して破棄
_timer.Stop();
_timer.Dispose();
};
}
}
}
Imports System.Timers
Class MainWindow
Public Sub New()
InitializeComponent()
SetupTimer()
End Sub
Private _timer As Timer
Private Sub SetupTimer()
' タイマの初期化と開始
_timer = New Timer(1000)
' ElapsedEventHandlerをラムダ式で定義
AddHandler _timer.Elapsed,
Sub(s, e)
' WPFではDispatcherを使ってUIスレッドでの処理を実行する
Me.Dispatcher.BeginInvoke(
New Action(
Sub()
Me.TextBlock1.Text = DateTime.Now.ToString("HH:mm:ss")
End Sub
))
End Sub
_timer.Start()
AddHandler Me.Closing,
Sub(s, e)
' 画面が閉じられるときに、タイマを停止して破棄
_timer.Stop()
_timer.Dispose()
End Sub
End Sub
End Class
C#のnamespace宣言「dotNetTips0374WpfCS」は、適切な名前に変更してほしい。
ここでは画面が保持しているDispatcherオブジェクト(=this.Dispatcher)を使っているが、その他のコントロールのもの(例えばTextBlock1.Dispatcherなど)を使っても同じである。
なお、タイマから呼び出されるイベントハンドラをこのようにラムダ式で記述するとコンパクトにまとまるが、ラムダ式の中にラムダ式(Actionデリゲート)が入ることになって少し分かりづらいかもしれない。「タイマにより一定時間間隔で処理を行うには?(スレッドタイマ編)」のサンプルコードのようにイベントハンドラを独立したメソッドにした方が分かりやすくなるだろう。
実行してみると次の画像のようになる。1秒ごとに時刻が書き換わる。
カテゴリ:クラスライブラリ 処理対象:タイマ
カテゴリ:WPF 処理対象:スレッド
使用ライブラリ:Timerクラス(System.Timers名前空間)
使用ライブラリ:ElapsedEventHandlerデリゲート(System.Timers名前空間)
関連TIPS:タイマにより一定時間間隔で処理を行うには?(Windowsタイマ編)
関連TIPS:タイマにより一定時間間隔で処理を行うには?(スレッドタイマ編)
関連TIPS:Windowsフォームで別スレッドからコントロールを操作するには?
関連TIPS:Windowsアプリケーションでファイルやディレクトリを監視するには?
更新履歴
【2018/12/05】Visual Studio 2017でコードの動作検証、ラムダ式の使用例、WPFアプリでの使用例、図版の追加、全般的な構成の変更などを行いました。
【2005/11/11】初版公開。
Copyright© Digital Advantage Corp. All Rights Reserved.