C# Timer Tickの使い方完全ガイド|イベントが動かない・初回実行できない原因と解決策

はじめに

C#で一定間隔ごとに処理を実行したいときによく使われるのが、TimerTickイベントです。

たとえば、Windows Formsアプリで「1秒ごとに現在時刻を表示する」「一定時間ごとに画面を更新する」「カウントダウンタイマーを作る」といった処理では、Timer Tickがよく使われます。

一方で、C#のTimerにはいくつか種類があります。

System.Windows.Forms.TimerSystem.Timers.TimerSystem.Threading.Timer、WPFで使うDispatcherTimerなどがあり、それぞれ動作するスレッドやイベント名が異なります。

そのため、次のような悩みが起きやすいです。

C#
timer.Tick += timer_Tick;
timer.Start();

と書いたのにTickイベントが動かない。

または、TimerをStartしたのに初回実行がすぐ行われない。

あるいは、Tick内でUIを更新しようとしてエラーになる。

この記事では、C#のTimer Tickの基本から、実装方法、イベントが動かない原因、初回実行できない理由、実践サンプル、UI更新時の注意点、停止・再開・破棄の書き方まで詳しく解説します。

1. C# Timer Tickとは?まず押さえる基本

1-1. Timer Tickは一定間隔で処理を実行するイベント

C#におけるTimer Tickとは、主にSystem.Windows.Forms.Timerで使われるイベントです。

Timerは、指定した時間間隔ごとにイベントを発生させるためのクラスです。Windows Formsでは、そのイベント名がTickです。

たとえば、1秒ごとに処理を実行したい場合は、Interval1000に設定して、Tickイベントに処理を書きます。

C#
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

この例では、TimerのTickイベントが発生するたびに、現在時刻をラベルに表示しています。

Tickという名前の通り、「一定間隔で刻む」ように処理が呼び出されるイメージです。

1-2. Tickイベントが発生するタイミングとIntervalの関係

Tickイベントの発生間隔は、Intervalプロパティで指定します。

Intervalの単位はミリ秒です。

C#
timer1.Interval = 1000;

この場合、1000ミリ秒、つまり1秒ごとにTickイベントが発生します。

よくある間違いは、Interval = 10と書いて「10秒」と思ってしまうことです。

C#
timer1.Interval = 10;

これは10秒ではなく、10ミリ秒です。

代表的な指定例は次の通りです。

C#
timer1.Interval = 500;    // 0.5秒
timer1.Interval = 1000; // 1秒
timer1.Interval = 5000; // 5秒
timer1.Interval = 60000; // 60秒、つまり1分

また、TimerはStart()を呼んだ瞬間にTickイベントを実行するわけではありません。基本的には、Start()を呼んだあと、指定したIntervalが経過してから最初のTickイベントが発生します。

つまり、次のコードでは、Start()直後ではなく約1秒後に最初のTickが発生します。

C#
timer1.Interval = 1000;
timer1.Start();

初回だけすぐ実行したい場合は、Timerとは別に処理をメソッド化して、Start()前に手動で1回呼び出すのが基本です。

1-3. 「Timer」「Tick」「Elapsed」の違い

C#でTimerについて調べていると、TickだけでなくElapsedというイベントも出てきます。

これは、使っているTimerの種類が違うためです。

Windows Formsでよく使うSystem.Windows.Forms.Timerでは、一定間隔ごとにTickイベントが発生します。

C#
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Tick += Timer_Tick;

一方、System.Timers.Timerでは、一定間隔ごとにElapsedイベントが発生します。

C#
System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += Timer_Elapsed;

つまり、TickElapsedはどちらも「一定間隔ごとに実行されるイベント」ですが、使うTimerクラスが異なります。

主な違いは次の通りです。

種類イベント名主な用途
System.Windows.Forms.TimerTickWindows FormsのUI更新
System.Timers.TimerElapsedサーバー処理、バックグラウンド処理
System.Threading.Timerコールバックスレッドプールでの定期実行
DispatcherTimerTickWPFのUI更新

C#ではTimerという名前のクラスが複数あるため、どの名前空間のTimerを使っているかを必ず確認しましょう。

1-4. C#でTimer Tickが使われる代表的な場面

C#のTimer Tickは、特にWindows Formsアプリでよく使われます。

代表的な用途は次のようなものです。

C#
// 1秒ごとに時刻を更新する
labelClock.Text = DateTime.Now.ToString("HH:mm:ss");
C#
// カウントダウンを進める
remainingSeconds--;
C#
// 一定間隔で状態を確認する
CheckStatus();
C#
// 画面上の表示を定期的に更新する
UpdateDisplay();

具体的には、次のような場面で使われます。

用途内容
時計表示1秒ごとに現在時刻を表示する
カウントダウン残り時間を1秒ずつ減らす
画面更新状態や進捗を定期的に反映する
入力監視一定間隔で入力状態をチェックする
ファイル監視一定間隔でファイルの存在や更新を確認する
簡易ポーリングAPIや外部状態を定期的に確認する

ただし、Timer Tickは万能ではありません。重い処理や高精度な時間制御には向いていない場合があります。

UI更新には便利ですが、長時間かかる処理や高精度な処理では、Timerの種類や実装方法を慎重に選ぶ必要があります。

2. C#で使えるTimerの種類とTickイベントの違い

2-1. System.Windows.Forms.Timer|WinFormsで使うTickイベント

System.Windows.Forms.Timerは、Windows Formsアプリで最もよく使われるTimerです。

特徴は、TickイベントがUIスレッドで実行されることです。

そのため、Tickイベントの中からフォーム上のラベルやボタンなどを直接更新できます。

C#
private System.Windows.Forms.Timer timer;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

Windows Formsでは、UIコントロールは基本的にUIスレッドから操作する必要があります。

System.Windows.Forms.TimerはUIスレッド上でTickイベントが実行されるため、ラベルの文字を変更するような処理に向いています。

ただし、UIスレッドで動作するということは、Tickイベントの中で重い処理を行うと画面が固まりやすいということでもあります。

次のような処理は避けるべきです。

C#
private void Timer_Tick(object sender, EventArgs e)
{
// 重い処理を直接書くのは避ける
Thread.Sleep(5000);
}

このようなコードを書くと、UIスレッドが止まり、フォームが応答しない状態になる可能性があります。

2-2. System.Timers.Timer|Elapsedイベントで定期実行するTimer

System.Timers.Timerは、サーバー処理やバックグラウンド処理でよく使われるTimerです。

Windows FormsのTickイベントではなく、Elapsedイベントを使います。

C#
private System.Timers.Timer timer;

private void StartTimer()
{
timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = true;
timer.Start();
}

private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine(DateTime.Now);
}

System.Timers.Timerの特徴は、通常、Elapsedイベントが別スレッドで実行される点です。

そのため、Windows FormsのUIを直接更新しようとすると問題が起きます。

C#
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// Windows Formsでは直接UIを更新しない
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

このような処理は、クロススレッド操作の例外につながることがあります。

Windows FormsのUIを更新したい場合は、InvokeBeginInvokeを使います。

C#
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (label1.InvokeRequired)
{
label1.BeginInvoke(new Action(() =>
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}));
}
else
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}
}

UI更新が目的なら、基本的にはSystem.Windows.Forms.Timerを使う方が簡単です。

2-3. System.Threading.Timer|スレッドプールで実行するTimer

System.Threading.Timerは、スレッドプール上でコールバックを実行するTimerです。

TickElapsedというイベントではなく、コンストラクタにコールバックメソッドを渡します。

C#
private System.Threading.Timer timer;

private void StartTimer()
{
timer = new System.Threading.Timer(
TimerCallback,
null,
0,
1000
);
}

private void TimerCallback(object state)
{
Console.WriteLine(DateTime.Now);
}

第3引数は初回実行までの待ち時間、第4引数は2回目以降の実行間隔です。

C#
new System.Threading.Timer(callback, state, dueTime, period);

たとえば、次のように書くと、すぐに初回実行され、その後1秒ごとに実行されます。

C#
timer = new System.Threading.Timer(
TimerCallback,
null,
0,
1000
);

System.Threading.Timerは軽量で、UIに依存しないバックグラウンド処理に向いています。

ただし、コールバックが重なって実行される可能性や、UI更新にInvokeが必要になる点には注意が必要です。

2-4. DispatcherTimer|WPFで使うTimer

WPFでは、DispatcherTimerを使うことが多いです。

DispatcherTimerはWPFのDispatcher上でTickイベントを発生させるため、WPFのUI要素を直接更新しやすいTimerです。

C#
private DispatcherTimer timer;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
textBlockTime.Text = DateTime.Now.ToString("HH:mm:ss");
}

Windows FormsのSystem.Windows.Forms.Timerと似ていますが、WPFではDispatcherTimerを使います。

Intervalの型も異なります。

Windows Forms Timerではミリ秒の整数を指定します。

C#
timer.Interval = 1000;

DispatcherTimerではTimeSpanを指定します。

C#
timer.Interval = TimeSpan.FromSeconds(1);

WPFでUI更新を目的にTimerを使う場合は、DispatcherTimerを選ぶのが基本です。

2-5. どのTimerを選ぶべきか用途別に比較

C#には複数のTimerがあるため、用途に合ったものを選ぶことが重要です。

用途おすすめのTimer理由
Windows Formsでラベルや画面を更新したいSystem.Windows.Forms.TimerTickがUIスレッドで実行される
WPFでUIを更新したいDispatcherTimerDispatcher上でTickが実行される
UIに依存しない定期処理をしたいSystem.Timers.Timerバックグラウンド処理に向いている
軽量な定期処理をしたいSystem.Threading.Timerスレッドプールで実行される
高精度な時間制御をしたいTimer以外も検討Timerは厳密なリアルタイム処理には不向き

Windows FormsでTimer Tickと言う場合、通常はSystem.Windows.Forms.TimerTickイベントを指します。

一方、コンソールアプリやサービスで定期実行したい場合は、System.Timers.TimerSystem.Threading.Timerを検討します。

迷った場合は、まず次の基準で選ぶとわかりやすいです。

Windows FormsのUI更新 → System.Windows.Forms.Timer
WPFのUI更新 → DispatcherTimer
バックグラウンド処理 → System.Timers.Timer または System.Threading.Timer

3. Windows Forms Timer Tickの基本的な使い方

3-1. デザイナーからTimerを配置して使う方法

Windows Formsでは、Visual StudioのデザイナーからTimerを配置できます。

手順は次の通りです。

  1. フォームをデザイナーで開く

  2. ツールボックスからTimerを選択する

  3. フォームに配置する

  4. プロパティウィンドウでIntervalを設定する

  5. イベント一覧からTickイベントを作成する

  6. Enabledtrueにするか、コードでStart()を呼ぶ

Timerは画面上に表示されるコントロールではありません。フォーム下部のコンポーネントトレイに表示されます。

デザイナーでTimerを配置すると、通常は次のようなフィールドが自動生成されます。

C#
private System.Windows.Forms.Timer timer1;

Tickイベントをダブルクリックすると、次のようなイベントハンドラが作成されます。

C#
private void timer1_Tick(object sender, EventArgs e)
{
// 一定間隔で実行したい処理を書く
}

あとは、Intervalを設定してTimerを開始すれば、Tickイベントが発生します。

C#
private void Form1_Load(object sender, EventArgs e)
{
timer1.Interval = 1000;
timer1.Start();
}

3-2. コードでTimerを生成する方法

Timerはデザイナーを使わず、コードだけで生成することもできます。

C#
private System.Windows.Forms.Timer timer;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

ポイントは、Timerをローカル変数ではなくフィールドとして保持することです。

悪い例は次のようなコードです。

C#
private void Form1_Load(object sender, EventArgs e)
{
var timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

このようにローカル変数だけでTimerを作ると、メソッド終了後に参照を保持できず、管理が難しくなります。

安定して使うためには、次のようにフォームのフィールドとして宣言しましょう。

C#
private System.Windows.Forms.Timer timer;

3-3. Intervalプロパティで実行間隔を設定する

Intervalは、Tickイベントが発生する間隔をミリ秒単位で指定するプロパティです。

C#
timer.Interval = 1000;

この場合、1秒ごとにTickイベントが発生します。

よく使うIntervalの値は次の通りです。

実行間隔Interval
0.1秒100
0.5秒500
1秒1000
5秒5000
10秒10000
1分60000

注意点として、System.Windows.Forms.TimerのTickはUIスレッドで処理されるため、指定したInterval通りに正確に実行されるとは限りません。

UIスレッドが忙しい場合、Tickイベントの発生が遅れることがあります。

たとえば、Tickイベント内で時間のかかる処理をしていると、次のTickの実行タイミングがずれます。

C#
private void Timer_Tick(object sender, EventArgs e)
{
// 重い処理
LongRunningProcess();

// UI更新
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

Timer Tickは「だいたい一定間隔で処理したい」用途には便利ですが、ミリ秒単位の正確な制御には向いていません。

3-4. Tickイベントハンドラを登録する

Timerをコードで使う場合、Tickイベントにイベントハンドラを登録する必要があります。

C#
timer.Tick += Timer_Tick;

イベントハンドラは次の形式で定義します。

C#
private void Timer_Tick(object sender, EventArgs e)
{
// Tick発生時の処理
}

この登録を忘れると、TimerをStartしても何も処理されません。

C#
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;

// これがないとTick時の処理が実行されない
timer.Tick += Timer_Tick;

timer.Start();

また、イベントハンドラの引数の型も重要です。

Windows Forms TimerのTickイベントでは、通常次の形になります。

C#
private void Timer_Tick(object sender, EventArgs e)

System.Timers.TimerのElapsedイベントとは異なります。

C#
private void Timer_Elapsed(object sender, ElapsedEventArgs e)

Timerの種類によってイベントハンドラの形式が異なるため、混同しないようにしましょう。

3-5. Start・Stop・Enabledの違い

Windows Forms Timerには、開始・停止を制御する方法が複数あります。

主に使うのは、Start()Stop()Enabledです。

C#
timer.Start();

これはTimerを開始します。内部的にはEnabled = trueにするのと近い動作です。

C#
timer.Stop();

これはTimerを停止します。内部的にはEnabled = falseにするのと近い動作です。

C#
timer.Enabled = true;
timer.Enabled = false;

Enabledプロパティを直接変更して、Timerの開始・停止を制御することもできます。

一般的には、読みやすさを重視して次のように使うことが多いです。

C#
// 開始
timer.Start();

// 停止
timer.Stop();

一方で、現在動いているかどうかを確認したいときはEnabledを使います。

C#
if (timer.Enabled)
{
timer.Stop();
}
else
{
timer.Start();
}

Start()Enabled = trueStop()Enabled = falseは似ていますが、コードの意図を明確にするなら、開始・停止にはStart()Stop()を使うのがおすすめです。

3-6. Timer Tickでラベルや画面表示を更新するサンプル

Windows Forms Timer Tickの基本サンプルとして、1秒ごとに現在時刻をラベルに表示してみましょう。

C#
public partial class Form1 : Form
{
private System.Windows.Forms.Timer timer;

public Form1()
{
InitializeComponent();

timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
}
}

このコードでは、フォームのコンストラクタでTimerを作成し、1秒ごとにTimer_Tickを呼び出しています。

Tickイベント内では、現在日時を取得してlabel1.Textに表示しています。

Windows Forms TimerはUIスレッドで動くため、このようなラベル更新は直接書けます。

ただし、Tickイベント内で重い処理をすると画面が固まる可能性があるため、UI更新用の軽い処理にとどめるのが安全です。

4. C# Timer Tickをコードだけで実装する方法

4-1. Timerインスタンスを作成する

コードだけでTimer Tickを実装する場合、まずTimerのインスタンスを作成します。

Windows Formsで使う場合は、名前空間の衝突を避けるために、明示的にSystem.Windows.Forms.Timerと書くと安全です。

C#
private System.Windows.Forms.Timer timer;

フォームの初期化時などでインスタンスを作成します。

C#
timer = new System.Windows.Forms.Timer();

完全な例は次の通りです。

C#
public partial class Form1 : Form
{
private System.Windows.Forms.Timer timer;

public Form1()
{
InitializeComponent();

timer = new System.Windows.Forms.Timer();
}
}

C#ではTimerというクラス名が複数の名前空間に存在します。

たとえば、次のようなTimerがあります。

C#
System.Windows.Forms.Timer
System.Timers.Timer
System.Threading.Timer

usingの指定によっては、どのTimerを指しているのか分かりにくくなることがあります。

そのため、Windows FormsのTickイベントを使いたい場合は、最初のうちは次のように完全修飾名で書くと間違いにくいです。

C#
private System.Windows.Forms.Timer timer;

4-2. Tickイベントに処理を追加する

Timerインスタンスを作成したら、Tickイベントに処理を追加します。

C#
timer.Tick += Timer_Tick;

そして、イベントハンドラを定義します。

C#
private void Timer_Tick(object sender, EventArgs e)
{
// 一定間隔で実行したい処理
}

たとえば、ラベルに現在時刻を表示する場合は次のようになります。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

Tickイベントは、Timerが有効になっていて、Intervalで指定した時間が経過するたびに呼び出されます。

ただし、イベントハンドラを登録しただけではTimerは動きません。

最後にStart()を呼ぶ必要があります。

C#
timer.Start();

4-3. timer.Tick += の書き方と意味

timer.Tick += Timer_Tick;という書き方は、C#のイベントにイベントハンドラを追加する構文です。

C#
timer.Tick += Timer_Tick;

これは、「timerのTickイベントが発生したら、Timer_Tickメソッドを呼び出す」という意味です。

次のように考えるとわかりやすいです。

timer.Tick      → 一定間隔で発生するイベント
+= → イベントに処理を追加する
Timer_Tick → 実行されるメソッド

イベントハンドラは、Timer Tickに合った引数を持つ必要があります。

C#
private void Timer_Tick(object sender, EventArgs e)
{
}

senderには、イベントを発生させたTimerオブジェクトが入ります。

C#
private void Timer_Tick(object sender, EventArgs e)
{
var currentTimer = sender as System.Windows.Forms.Timer;
}

eにはイベントデータが入りますが、Windows Forms TimerのTickでは通常あまり使いません。

また、イベントは複数登録できます。

C#
timer.Tick += Timer_Tick;
timer.Tick += Another_Tick;

この場合、Tickが発生すると両方のメソッドが呼び出されます。

意図せず複数回登録すると、同じ処理が複数回実行される原因になるため注意しましょう。

4-4. ラムダ式でTickイベントを記述する方法

Tickイベントは、ラムダ式で書くこともできます。

C#
timer.Tick += (sender, e) =>
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
};

短い処理であれば、ラムダ式を使うとコードをコンパクトにできます。

完全な例は次の通りです。

C#
private System.Windows.Forms.Timer timer;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;

timer.Tick += (s, args) =>
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
};

timer.Start();
}

ただし、ラムダ式には注意点もあります。

後からイベントを解除したい場合、匿名のラムダ式は解除しにくいです。

たとえば、次のように書いた場合です。

C#
timer.Tick += (s, e) =>
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
};

後から同じラムダ式を指定して解除することはできません。

イベントを後から解除する可能性がある場合は、通常のメソッドとして定義する方が扱いやすいです。

C#
timer.Tick += Timer_Tick;
timer.Tick -= Timer_Tick;

ラムダ式は便利ですが、使いどころを選ぶことが大切です。

4-5. 複数のTimerを使うときの注意点

C#では、複数のTimerを同時に使うこともできます。

たとえば、1秒ごとの時計表示用Timerと、5秒ごとの状態チェック用Timerを分けることができます。

C#
private System.Windows.Forms.Timer clockTimer;
private System.Windows.Forms.Timer statusTimer;

private void Form1_Load(object sender, EventArgs e)
{
clockTimer = new System.Windows.Forms.Timer();
clockTimer.Interval = 1000;
clockTimer.Tick += ClockTimer_Tick;
clockTimer.Start();

statusTimer = new System.Windows.Forms.Timer();
statusTimer.Interval = 5000;
statusTimer.Tick += StatusTimer_Tick;
statusTimer.Start();
}

private void ClockTimer_Tick(object sender, EventArgs e)
{
labelClock.Text = DateTime.Now.ToString("HH:mm:ss");
}

private void StatusTimer_Tick(object sender, EventArgs e)
{
CheckStatus();
}

複数Timerを使う場合は、役割ごとに名前を明確にしましょう。

悪い例は次のような名前です。

C#
timer1
timer2
timer3

小さなサンプルでは問題ありませんが、処理が増えると何のTimerかわからなくなります。

おすすめは、目的がわかる名前にすることです。

C#
clockTimer
countdownTimer
statusCheckTimer
autoSaveTimer

また、Windows Forms TimerはUIスレッドで動くため、複数のTimerが同時に重い処理を行うと、画面が固まりやすくなります。

複数のTimerを使う場合でも、Tick内の処理は短く保つことが大切です。

5. Timer Tickイベントが動かない原因と解決策

5-1. StartまたはEnabled=trueを呼び忘れている

Timer Tickが動かない原因として最も多いのが、Timerを開始していないケースです。

次のコードでは、Timerを作成してイベントも登録していますが、Start()を呼んでいません。

C#
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;

// timer.Start() がない

この状態ではTickイベントは発生しません。

解決策は、Start()を呼ぶことです。

C#
timer.Start();

または、Enabledtrueにします。

C#
timer.Enabled = true;

一般的には、開始の意図がわかりやすいStart()を使うのがおすすめです。

C#
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();

デザイナーでTimerを配置している場合は、プロパティのEnabledfalseのままになっていないか確認しましょう。

5-2. Intervalが長すぎる・ミリ秒指定を誤解している

Timer Tickが動かないように見える原因として、Intervalの指定ミスがあります。

Intervalはミリ秒です。

C#
timer.Interval = 10000;

これは10秒です。

もし「1秒ごとに動くはず」と思っていた場合、実際には10秒待たないとTickが発生しません。

また、次のように大きな値を設定していると、しばらく何も起きないように見えます。

C#
timer.Interval = 60000; // 1分

デバッグ時は、まず短めのIntervalで確認するとわかりやすいです。

C#
timer.Interval = 1000; // 1秒

逆に、Interval = 1のように極端に短い間隔を指定すると、UIスレッドに負荷がかかり、期待通りに動かないことがあります。

Windows Forms Timerでは、画面更新用途なら100ミリ秒から1000ミリ秒程度が現実的なことが多いです。

5-3. Tickイベントが登録されていない

TimerをStartしても、Tickイベントに処理が登録されていなければ、何も起きません。

悪い例です。

C#
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Start();

このコードでは、Timerは動いていますが、Tick時に実行する処理がありません。

解決策は、Tickイベントを登録することです。

C#
timer.Tick += Timer_Tick;

完全なコードは次のようになります。

C#
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();

イベントハンドラも定義します。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

デザイナーでTimerを使っている場合は、プロパティウィンドウのイベント一覧でTickに正しいメソッドが設定されているか確認しましょう。

5-4. Timerインスタンスがローカル変数で破棄されている

Timerをメソッド内のローカル変数として作成していると、後から参照できず、停止や破棄などの管理ができません。

C#
private void StartTimer()
{
var timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

このようなコードは、見た目上は動く場合もありますが、Timerのライフサイクルを管理しにくくなります。

安全な書き方は、Timerをフィールドとして保持することです。

C#
private System.Windows.Forms.Timer timer;

private void StartTimer()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

フォーム終了時に停止・破棄する場合も、フィールドで持っていれば簡単です。

C#
protected override void OnFormClosed(FormClosedEventArgs e)
{
timer?.Stop();
timer?.Dispose();
base.OnFormClosed(e);
}

Timer Tickが突然動かなくなる、Stopできない、Disposeできないといった問題を避けるためにも、Timerはフィールドで管理しましょう。

5-5. UIスレッド以外でWindows.Forms.Timerを使っている

System.Windows.Forms.Timerは、Windows Formsのメッセージループに依存して動作します。

そのため、UIスレッド以外で使う用途には向いていません。

たとえば、コンソールアプリやバックグラウンドスレッドでSystem.Windows.Forms.Timerを使っても、期待通りにTickが発生しないことがあります。

Windows FormsのUI上で使う場合は問題ありません。

C#
private System.Windows.Forms.Timer timer;

しかし、バックグラウンド処理で定期実行したいなら、次のようなTimerを使う方が適しています。

C#
System.Timers.Timer
System.Threading.Timer

Windows Forms Timerは、あくまでWinFormsのUI向けです。

コンソールアプリ、Windowsサービス、バックグラウンド処理では、System.Timers.TimerSystem.Threading.Timerを検討しましょう。

5-6. Tick内の処理が重くUIスレッドをブロックしている

Windows Forms TimerのTickイベントはUIスレッドで実行されます。

そのため、Tick内で重い処理を行うと、UIスレッドがブロックされます。

悪い例です。

C#
private void Timer_Tick(object sender, EventArgs e)
{
// 時間のかかる処理
Thread.Sleep(5000);

label1.Text = "完了";
}

このコードでは、Tickイベントが発生するたびに5秒間UIが止まります。

フォームの移動、ボタン操作、再描画などができなくなる可能性があります。

解決策は、重い処理をTick内に直接書かないことです。

非同期処理に分ける例です。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
string result = await Task.Run(() => LongRunningProcess());
label1.Text = result;
}
finally
{
timer.Start();
}
}

この例では、重い処理をTask.Runで別スレッドに逃がし、UI更新だけをTickイベント側で行っています。

さらに、処理中はTimerをStopして、処理の重複実行を防いでいます。

5-7. 例外が発生して処理が止まっている

Tickイベント内で例外が発生していると、処理が途中で止まります。

たとえば、ファイル読み込みやネットワークアクセス、null参照などで例外が発生することがあります。

C#
private void Timer_Tick(object sender, EventArgs e)
{
string text = File.ReadAllText("data.txt");
label1.Text = text;
}

このコードでは、data.txtが存在しない場合に例外が発生します。

Timer Tick内では、必要に応じて例外処理を入れましょう。

C#
private void Timer_Tick(object sender, EventArgs e)
{
try
{
string text = File.ReadAllText("data.txt");
label1.Text = text;
}
catch (Exception ex)
{
label1.Text = "読み込みエラー";
Console.WriteLine(ex.Message);
}
}

特に、Timerは定期的に実行されるため、例外が毎回発生するとログが大量に出たり、アプリの動作が不安定になったりします。

例外が発生する可能性がある処理は、Tick内で必ず保護しましょう。

5-8. フォームやコントロールが破棄された後にTimerを使っている

フォームを閉じた後や、コントロールが破棄された後にTimerが動き続けていると、エラーの原因になります。

たとえば、Timer Tick内でラベルを更新している場合、フォームが閉じられた後もTimerが動いていると、破棄済みコントロールにアクセスする可能性があります。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

フォーム終了時にはTimerを止めるのが安全です。

C#
protected override void OnFormClosing(FormClosingEventArgs e)
{
timer?.Stop();
base.OnFormClosing(e);
}

さらに、不要になったTimerはDisposeします。

C#
protected override void OnFormClosed(FormClosedEventArgs e)
{
timer?.Stop();
timer?.Dispose();
base.OnFormClosed(e);
}

デザイナーで配置したTimerはフォームのコンポーネントとして管理されるため、自動的に破棄されることもありますが、コードで作成したTimerは明示的に管理するのがわかりやすいです。

6. Timer Tickで初回実行できない理由と対処法

6-1. TimerはStart直後ではなくInterval経過後に初回実行される

C#のTimer Tickでよくある疑問が、「Startしたのにすぐ実行されない」というものです。

Windows Forms Timerでは、基本的にStart()を呼んだ直後にTickイベントが発生するわけではありません。

次のコードを見てください。

C#
timer.Interval = 1000;
timer.Start();

この場合、Start()直後ではなく、約1秒後に最初のTickイベントが発生します。

これはTimerの仕様として自然な動作です。

Timerは「指定したIntervalごとにイベントを発生させる」ため、初回もIntervalの経過を待ちます。

そのため、「アプリ起動直後にすぐ表示を更新したい」「Startボタンを押した瞬間に処理したい」という場合は、Timer Tickだけに任せると初回実行が遅れます。

6-2. 初回だけすぐ実行したい場合は処理をメソッド化する

初回だけすぐ実行し、その後はTimer Tickで定期実行したい場合は、処理をメソッド化するのがおすすめです。

悪い例は、処理をTickイベント内に直接書いてしまうことです。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

この書き方でも動きますが、初回に同じ処理を呼び出したいときに不便です。

改善例として、実際の処理を別メソッドに分離します。

C#
private void UpdateClock()
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

private void Timer_Tick(object sender, EventArgs e)
{
UpdateClock();
}

こうしておけば、Timer開始前にUpdateClock()を手動で呼び出せます。

C#
UpdateClock();
timer.Start();

この書き方にすると、初回実行と定期実行で同じ処理を使い回せます。

6-3. Start前に手動で処理を1回呼び出す実装例

初回実行をすぐ行いたい場合の基本形は、次のようになります。

C#
private System.Windows.Forms.Timer timer;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;

UpdateClock(); // 初回だけすぐ実行
timer.Start(); // 以降は1秒ごとに実行
}

private void Timer_Tick(object sender, EventArgs e)
{
UpdateClock();
}

private void UpdateClock()
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

このコードでは、フォーム読み込み時にまずUpdateClock()を呼んでいます。

その後、TimerをStartすることで、以降は1秒ごとにUpdateClock()が実行されます。

このパターンは非常によく使います。

時計表示、画面状態更新、データ再読み込みなど、初回にも同じ処理を実行したい場合に便利です。

6-4. 初回実行後にTimerで定期実行する実装例

たとえば、アプリ起動直後に状態をチェックし、その後は5秒ごとに状態をチェックしたい場合は、次のように書けます。

C#
private System.Windows.Forms.Timer statusTimer;

private void Form1_Load(object sender, EventArgs e)
{
statusTimer = new System.Windows.Forms.Timer();
statusTimer.Interval = 5000;
statusTimer.Tick += StatusTimer_Tick;

CheckStatus(); // 初回実行
statusTimer.Start(); // 以降は5秒ごと
}

private void StatusTimer_Tick(object sender, EventArgs e)
{
CheckStatus();
}

private void CheckStatus()
{
// 状態確認処理
labelStatus.Text = DateTime.Now.ToString("HH:mm:ss") + " に確認しました";
}

この実装では、初回実行の遅れがありません。

Timerの初回Tickを待つのではなく、必要な処理を明示的に1回呼び出しています。

Timer Tickの初回実行問題は、Timer側で無理に解決しようとするより、処理をメソッド化して手動実行する方がシンプルです。

6-5. 初回だけ別処理をしたい場合のフラグ管理

初回だけ通常とは違う処理をしたい場合は、フラグを使って管理できます。

C#
private bool isFirstTick = true;

private void Timer_Tick(object sender, EventArgs e)
{
if (isFirstTick)
{
isFirstTick = false;
label1.Text = "初回Tickです";
return;
}

label1.Text = "通常Tick: " + DateTime.Now.ToString("HH:mm:ss");
}

ただし、このコードでは「最初のTick」はStart後、Intervalが経過した後に発生します。

Start直後に初回処理をしたい場合は、やはり手動で呼び出す方が適しています。

C#
private bool isFirstRun = true;

private void ExecuteProcess()
{
if (isFirstRun)
{
isFirstRun = false;
label1.Text = "初回実行です";
}
else
{
label1.Text = "通常実行: " + DateTime.Now.ToString("HH:mm:ss");
}
}

private void Timer_Tick(object sender, EventArgs e)
{
ExecuteProcess();
}

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;

ExecuteProcess(); // 初回をすぐ実行
timer.Start();
}

初回だけ別処理をしたい場合も、Timer Tick内にすべてを書かず、実行メソッドを分けると管理しやすくなります。

6-6. System.Timers.TimerでAutoResetを使う場合の注意点

System.Timers.Timerでは、AutoResetプロパティによって繰り返し実行するかどうかを制御できます。

C#
timer.AutoReset = true;

AutoReset = trueの場合、ElapsedイベントはIntervalごとに繰り返し発生します。

C#
var timer = new System.Timers.Timer(1000);
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = true;
timer.Start();

一方、AutoReset = falseの場合、Elapsedイベントは基本的に1回だけ発生します。

C#
timer.AutoReset = false;

「Timerが1回しか実行されない」という場合、System.Timers.TimerAutoResetfalseになっていないか確認しましょう。

また、System.Timers.Timerでも、Start直後にElapsedが即時実行されるわけではありません。初回実行は通常、Interval経過後です。

初回だけすぐ実行したい場合は、Windows Forms Timerと同じように、処理メソッドを手動で呼び出すとわかりやすいです。

C#
CheckStatus();
timer.Start();

System.Threading.Timerであれば、初回実行までの待ち時間を0に設定することで、すぐに初回実行できます。

C#
timer = new System.Threading.Timer(
Callback,
null,
0,
1000
);

7. Timer Tickの実践サンプル

7-1. 1秒ごとに現在時刻を表示する

最も基本的なTimer Tickのサンプルは、1秒ごとに現在時刻を表示する処理です。

C#
public partial class Form1 : Form
{
private System.Windows.Forms.Timer clockTimer;

public Form1()
{
InitializeComponent();

clockTimer = new System.Windows.Forms.Timer();
clockTimer.Interval = 1000;
clockTimer.Tick += ClockTimer_Tick;

UpdateClock();
clockTimer.Start();
}

private void ClockTimer_Tick(object sender, EventArgs e)
{
UpdateClock();
}

private void UpdateClock()
{
labelClock.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
}
}

このサンプルでは、UpdateClock()を用意して、初回実行とTick時の処理を共通化しています。

clockTimer.Start()の前にUpdateClock()を呼んでいるため、フォーム表示直後から時刻が表示されます。

7-2. カウントダウンタイマーを作る

次は、10秒から0秒までカウントダウンするTimer Tickのサンプルです。

C#
public partial class Form1 : Form
{
private System.Windows.Forms.Timer countdownTimer;
private int remainingSeconds = 10;

public Form1()
{
InitializeComponent();

countdownTimer = new System.Windows.Forms.Timer();
countdownTimer.Interval = 1000;
countdownTimer.Tick += CountdownTimer_Tick;

labelCountdown.Text = remainingSeconds.ToString();
}

private void buttonStart_Click(object sender, EventArgs e)
{
remainingSeconds = 10;
labelCountdown.Text = remainingSeconds.ToString();
countdownTimer.Start();
}

private void CountdownTimer_Tick(object sender, EventArgs e)
{
remainingSeconds--;
labelCountdown.Text = remainingSeconds.ToString();

if (remainingSeconds <= 0)
{
countdownTimer.Stop();
MessageBox.Show("時間です");
}
}
}

ポイントは、残り秒数をフィールドで管理することです。

C#
private int remainingSeconds = 10;

Tickイベントが発生するたびに値を1ずつ減らし、0になったらTimerを停止します。

C#
if (remainingSeconds <= 0)
{
countdownTimer.Stop();
}

Tick内でStop()を呼んでも問題ありません。

7-3. 一定時間ごとにファイルや状態をチェックする

一定間隔でファイルの存在をチェックする例です。

C#
private System.Windows.Forms.Timer fileCheckTimer;

private void Form1_Load(object sender, EventArgs e)
{
fileCheckTimer = new System.Windows.Forms.Timer();
fileCheckTimer.Interval = 5000;
fileCheckTimer.Tick += FileCheckTimer_Tick;

CheckFile();
fileCheckTimer.Start();
}

private void FileCheckTimer_Tick(object sender, EventArgs e)
{
CheckFile();
}

private void CheckFile()
{
string path = @"C:\temp\data.txt";

if (File.Exists(path))
{
labelStatus.Text = "ファイルがあります";
}
else
{
labelStatus.Text = "ファイルがありません";
}
}

この例では、5秒ごとに指定ファイルの存在を確認します。

ただし、ファイルアクセスで例外が発生する可能性がある処理では、try-catchを入れておくと安全です。

C#
private void CheckFile()
{
try
{
string path = @"C:\temp\data.txt";
labelStatus.Text = File.Exists(path)
? "ファイルがあります"
: "ファイルがありません";
}
catch (Exception ex)
{
labelStatus.Text = "確認エラー";
Console.WriteLine(ex.Message);
}
}

状態チェックやファイル確認のような処理では、Tick内に直接処理を書くより、CheckFile()のようなメソッドに分けると保守しやすくなります。

7-4. ボタン操作でTimerを開始・停止する

Timerをボタン操作で開始・停止する例です。

C#
private System.Windows.Forms.Timer timer;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
}

private void buttonStart_Click(object sender, EventArgs e)
{
timer.Start();
labelStatus.Text = "Timerを開始しました";
}

private void buttonStop_Click(object sender, EventArgs e)
{
timer.Stop();
labelStatus.Text = "Timerを停止しました";
}

private void Timer_Tick(object sender, EventArgs e)
{
labelTime.Text = DateTime.Now.ToString("HH:mm:ss");
}

StartボタンでTimerを開始し、StopボタンでTimerを停止します。

現在の状態に応じて1つのボタンで開始・停止を切り替えることもできます。

C#
private void buttonToggle_Click(object sender, EventArgs e)
{
if (timer.Enabled)
{
timer.Stop();
buttonToggle.Text = "開始";
}
else
{
timer.Start();
buttonToggle.Text = "停止";
}
}

Timerが動作中かどうかは、timer.Enabledで確認できます。

7-5. 一定回数実行したらTimerを停止する

Timer Tickを一定回数だけ実行して停止したい場合は、カウンターを使います。

C#
private System.Windows.Forms.Timer timer;
private int tickCount = 0;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
tickCount++;

label1.Text = $"{tickCount}回目のTickです";

if (tickCount >= 5)
{
timer.Stop();
label1.Text += " 停止しました";
}
}

この例では、5回Tickが発生したらTimerを停止します。

一定回数だけ処理を実行したい場合に便利です。

複数回実行してから止める処理では、カウンター変数をフィールドとして持つことがポイントです。

C#
private int tickCount = 0;

ローカル変数にすると、Tickのたびに値が初期化されてしまうため、回数を正しく数えられません。

7-6. 非同期処理とTimer Tickを組み合わせる

Timer Tickで非同期処理を呼び出すこともあります。

たとえば、一定間隔で重い処理を実行し、完了したらUIに結果を表示する例です。

C#
private System.Windows.Forms.Timer timer;
private bool isRunning = false;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 5000;
timer.Tick += Timer_Tick;
timer.Start();
}

private async void Timer_Tick(object sender, EventArgs e)
{
if (isRunning)
{
return;
}

isRunning = true;

try
{
labelStatus.Text = "処理中...";

string result = await Task.Run(() =>
{
Thread.Sleep(3000);
return "処理完了: " + DateTime.Now.ToString("HH:mm:ss");
});

labelStatus.Text = result;
}
catch (Exception ex)
{
labelStatus.Text = "エラー: " + ex.Message;
}
finally
{
isRunning = false;
}
}

ここで重要なのは、isRunningフラグです。

非同期処理が終わる前に次のTickが来ると、同じ処理が重複して実行される可能性があります。

そのため、処理中なら次のTickを無視するようにしています。

C#
if (isRunning)
{
return;
}

または、処理中はTimer自体を停止する方法もあります。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
await DoSomethingAsync();
}
finally
{
timer.Start();
}
}

非同期処理とTimer Tickを組み合わせる場合は、重複実行に特に注意しましょう。

8. Timer TickでUIを更新するときの注意点

8-1. Windows.Forms.TimerはUIスレッドで動作する

System.Windows.Forms.TimerのTickイベントは、Windows FormsのUIスレッドで実行されます。

そのため、Tickイベント内でラベルやテキストボックスを直接更新できます。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

これはWindows Forms Timerの大きなメリットです。

System.Timers.TimerSystem.Threading.Timerでは、別スレッドからイベントやコールバックが実行されることがあるため、UIを直接更新すると例外になる場合があります。

Windows FormsでUI更新が目的なら、まずはSystem.Windows.Forms.Timerを使うのが簡単です。

ただし、UIスレッドで動くということは、Tick内の処理が重いとUI全体が止まるということでもあります。

UI更新は軽く、短く済ませるのが基本です。

8-2. 重い処理をTick内に直接書かない

Timer Tickで最も避けたいのは、重い処理を直接書くことです。

C#
private void Timer_Tick(object sender, EventArgs e)
{
// 重い処理を直接実行
LoadLargeFile();
CalculateHeavyData();
UpdateScreen();
}

このようなコードでは、Timer TickのたびにUIスレッドが長時間占有されます。

結果として、フォームが固まる、ボタンが反応しない、画面更新が遅れるといった問題が起きます。

改善するには、重い処理をバックグラウンドで実行し、UI更新だけをUIスレッドで行います。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
var result = await Task.Run(() =>
{
return CalculateHeavyData();
});

labelResult.Text = result;
}
finally
{
timer.Start();
}
}

この例では、重い計算をTask.Runで実行し、結果をラベルに表示しています。

Timer Tick内で非同期処理を使う場合は、重複実行を避けるためにTimerを一時停止するか、フラグで制御しましょう。

8-3. async/awaitを使う場合の注意点

Timer Tickイベントにasyncを付ける場合、通常はasync voidになります。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
await DoSomethingAsync();
}

イベントハンドラではasync voidが使われますが、例外処理には注意が必要です。

非同期処理中に例外が発生すると、アプリ全体に影響する可能性があります。

必ずtry-catchを入れることをおすすめします。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
try
{
await DoSomethingAsync();
}
catch (Exception ex)
{
labelStatus.Text = "エラー: " + ex.Message;
}
}

また、非同期処理がIntervalより長い場合、次のTickが来て同じ処理が重複する可能性があります。

たとえば、Intervalが1秒で、処理に3秒かかる場合です。

C#
timer.Interval = 1000;

この場合、前回の処理が終わる前に次のTickが発生する可能性があります。

重複を防ぐには、フラグを使います。

C#
private bool isProcessing = false;

private async void Timer_Tick(object sender, EventArgs e)
{
if (isProcessing)
{
return;
}

isProcessing = true;

try
{
await DoSomethingAsync();
}
finally
{
isProcessing = false;
}
}

または、処理中はTimerを止めます。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
await DoSomethingAsync();
}
finally
{
timer.Start();
}
}

どちらの方法でも、重複実行を防げます。

8-4. 別スレッドからUIを更新する場合はInvokeが必要

System.Timers.TimerSystem.Threading.Timerを使う場合、処理がUIスレッド以外で実行されることがあります。

その状態でWindows Formsのコントロールを直接更新すると、クロススレッド操作の例外が発生することがあります。

悪い例です。

C#
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

この場合、label1を作成したUIスレッドとは別のスレッドからアクセスしている可能性があります。

安全にUIを更新するには、InvokeまたはBeginInvokeを使います。

C#
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (label1.InvokeRequired)
{
label1.BeginInvoke(new Action(() =>
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}));
}
else
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}
}

Windows Forms Timerを使っている場合は基本的にUIスレッドでTickが実行されるため、このようなInvokeは不要です。

ただし、バックグラウンド処理の結果をUIに反映する場合は、どのスレッドからUIを更新しているかを意識しましょう。

8-5. 処理がIntervalより長い場合に起きる問題

Timer Tickの処理時間がIntervalより長い場合、いくつかの問題が起きます。

たとえば、Intervalが1秒で、Tick内の処理に3秒かかる場合です。

C#
timer.Interval = 1000;
C#
private void Timer_Tick(object sender, EventArgs e)
{
LongRunningProcess(); // 3秒かかる
}

Windows Forms TimerではUIスレッドがブロックされるため、画面が固まり、次のTickも遅れます。

System.Timers.TimerSystem.Threading.Timerでは、条件によっては前回処理が終わる前に次の処理が始まる可能性があります。

このような場合は、次の対策が必要です。

C#
private bool isRunning = false;

private void Timer_Tick(object sender, EventArgs e)
{
if (isRunning)
{
return;
}

isRunning = true;

try
{
LongRunningProcess();
}
finally
{
isRunning = false;
}
}

または、処理中はTimerを停止します。

C#
private void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
LongRunningProcess();
}
finally
{
timer.Start();
}
}

この方法では、処理の重複を防げます。

ただし、処理時間も含めて次回実行までの間隔が延びるため、「厳密に1秒ごと」ではなくなります。

Timer Tickでは、Intervalと処理時間の関係を必ず考慮しましょう。

9. Timer Tickの停止・再開・破棄の正しい書き方

9-1. Stopメソッドで一時停止する

Timerを一時停止するには、Stop()メソッドを使います。

C#
timer.Stop();

Stop()を呼ぶと、以降のTickイベントは発生しなくなります。

ただし、Timerオブジェクト自体が破棄されるわけではありません。

そのため、再びStart()を呼べば再開できます。

C#
timer.Start();

一時停止の例です。

C#
private void buttonStop_Click(object sender, EventArgs e)
{
timer.Stop();
labelStatus.Text = "停止中";
}

Tickイベントの中で条件に応じて停止することもできます。

C#
private void Timer_Tick(object sender, EventArgs e)
{
count++;

if (count >= 10)
{
timer.Stop();
}
}

Tick内でStop()を呼んでも問題ありません。

9-2. Startメソッドで再開する

停止したTimerを再開するには、Start()を呼びます。

C#
timer.Start();

再開後も、TickイベントはInterval経過後に発生します。

C#
private void buttonStart_Click(object sender, EventArgs e)
{
timer.Start();
labelStatus.Text = "実行中";
}

一時停止・再開を行うボタンの例です。

C#
private void buttonToggle_Click(object sender, EventArgs e)
{
if (timer.Enabled)
{
timer.Stop();
buttonToggle.Text = "再開";
}
else
{
timer.Start();
buttonToggle.Text = "停止";
}
}

Timerが現在動いているかどうかは、Enabledで確認できます。

C#
if (timer.Enabled)
{
// Timerは動作中
}

9-3. Enabledプロパティで制御する

TimerはEnabledプロパティでも開始・停止できます。

C#
timer.Enabled = true;  // 開始
timer.Enabled = false; // 停止

Start()Stop()を使った場合と似た動作になります。

C#
timer.Start(); // timer.Enabled = true と近い
timer.Stop(); // timer.Enabled = false と近い

状態の確認にはEnabledが便利です。

C#
if (timer.Enabled)
{
labelStatus.Text = "Timerは動作中です";
}
else
{
labelStatus.Text = "Timerは停止中です";
}

コードの読みやすさを考えると、開始・停止にはStart()Stop()、状態確認にはEnabledを使うのがおすすめです。

C#
timer.Start();

if (timer.Enabled)
{
timer.Stop();
}

9-4. DisposeでTimerを破棄する

Timerが不要になったら、Dispose()で破棄します。

C#
timer.Dispose();

破棄前には、Timerを停止しておくと安全です。

C#
timer.Stop();
timer.Dispose();

フォームでコードから作成したTimerの場合、フォーム終了時に破棄する例は次の通りです。

C#
protected override void OnFormClosed(FormClosedEventArgs e)
{
if (timer != null)
{
timer.Stop();
timer.Dispose();
timer = null;
}

base.OnFormClosed(e);
}

より簡潔に書くなら、null条件演算子を使えます。

C#
protected override void OnFormClosed(FormClosedEventArgs e)
{
timer?.Stop();
timer?.Dispose();

base.OnFormClosed(e);
}

Timerは内部でリソースを使うため、不要になったら適切に破棄することが大切です。

9-5. フォーム終了時にTimerを止めるべき理由

フォーム終了時にTimerを止めるべき理由は、破棄済みのフォームやコントロールにアクセスするのを防ぐためです。

たとえば、Timer Tick内でラベルを更新しているとします。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

フォームが閉じられた後もTimerが動いていると、すでに破棄されたlabel1にアクセスしようとする可能性があります。

その結果、例外や予期しない動作につながります。

フォーム終了時には、次のようにTimerを停止しましょう。

C#
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
timer?.Stop();
}

コードで作成したTimerなら、フォーム終了後にDisposeするのも良いです。

C#
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
timer?.Dispose();
}

安全性を考えると、フォームのライフサイクルに合わせてTimerも管理することが重要です。

10. Timer Tickでよくあるエラー・疑問

10-1. Tickが1回しか実行されないのはなぜか

Windows Forms TimerでTickが1回しか実行されない場合、Tick内でTimerを停止していないか確認しましょう。

C#
private void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();
}

このコードでは、最初のTickでTimerを停止するため、1回しか実行されません。

また、どこかでEnabled = falseにしている場合も同様です。

C#
timer.Enabled = false;

System.Timers.Timerを使っている場合は、AutoResetを確認しましょう。

C#
timer.AutoReset = false;

AutoResetfalseの場合、Elapsedイベントは基本的に1回だけ実行されます。

繰り返し実行したい場合は、次のようにします。

C#
timer.AutoReset = true;

Windows Forms TimerのTickを繰り返し実行したい場合は、TimerをStopしない限り、Intervalごとに発生します。

10-2. Tickの間隔がずれるのはなぜか

Timer Tickの間隔がずれる主な理由は、処理に時間がかかっているためです。

Windows Forms TimerはUIスレッドで動作するため、UIスレッドが忙しいとTickイベントの発生が遅れます。

たとえば、Tick内で重い処理をしている場合です。

C#
private void Timer_Tick(object sender, EventArgs e)
{
LongRunningProcess();
}

また、フォームの再描画やユーザー操作などでUIスレッドが忙しい場合も、Tickのタイミングがずれることがあります。

Timerは厳密なリアルタイム処理には向いていません。

時計表示のような用途では、カウントを単純に増やすより、現在時刻を毎回取得する方がずれにくくなります。

悪い例です。

C#
seconds++;
label1.Text = seconds.ToString();

より良い例です。

C#
label1.Text = DateTime.Now.ToString("HH:mm:ss");

カウントダウンでも、厳密に管理したい場合は開始時刻と現在時刻の差分から残り時間を計算する方法があります。

10-3. Tickイベントを後から解除する方法

Tickイベントは、-=で解除できます。

C#
timer.Tick -= Timer_Tick;

たとえば、特定の条件でイベント処理を外したい場合は次のように書けます。

C#
private void Timer_Tick(object sender, EventArgs e)
{
count++;

if (count >= 5)
{
timer.Tick -= Timer_Tick;
timer.Stop();
}
}

イベントを解除すると、そのメソッドはTick発生時に呼ばれなくなります。

ただし、ラムダ式で登録したイベントは解除しにくいので注意が必要です。

C#
timer.Tick += (s, e) =>
{
label1.Text = DateTime.Now.ToString();
};

このような匿名ラムダを後から解除するには、同じデリゲートインスタンスを保持しておく必要があります。

解除する予定があるなら、通常のメソッドを使うのが簡単です。

C#
timer.Tick += Timer_Tick;
timer.Tick -= Timer_Tick;

10-4. Timerを複数使ってもよいか

Timerを複数使うこと自体は問題ありません。

たとえば、時計表示用、状態チェック用、自動保存用にTimerを分けることができます。

C#
private System.Windows.Forms.Timer clockTimer;
private System.Windows.Forms.Timer autoSaveTimer;

ただし、複数のTimerを使う場合は、次の点に注意しましょう。

1つ目は、名前を明確にすることです。

C#
clockTimer
statusCheckTimer
autoSaveTimer

2つ目は、Tick内の処理を重くしないことです。

Windows Forms TimerはUIスレッドで動くため、複数のTimerが重い処理を行うとUIが固まりやすくなります。

3つ目は、停止や破棄を忘れないことです。

C#
clockTimer?.Stop();
autoSaveTimer?.Stop();

clockTimer?.Dispose();
autoSaveTimer?.Dispose();

Timerが増えすぎると管理が難しくなるため、同じタイミングで実行できる処理は1つのTimerにまとめることも検討しましょう。

10-5. Tick内でStopしても問題ないか

Tickイベント内でStop()を呼んでも問題ありません。

一定条件に達したらTimerを止める処理ではよく使います。

C#
private void Timer_Tick(object sender, EventArgs e)
{
count++;

if (count >= 10)
{
timer.Stop();
}
}

カウントダウンタイマーでもよく使います。

C#
private void CountdownTimer_Tick(object sender, EventArgs e)
{
remainingSeconds--;

if (remainingSeconds <= 0)
{
countdownTimer.Stop();
MessageBox.Show("終了しました");
}
}

また、Tick内で非同期処理を始める前にTimerを一時停止するのも有効です。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
await DoSomethingAsync();
}
finally
{
timer.Start();
}
}

このようにすると、処理中に次のTickが発生して重複実行されるのを防げます。

10-6. TickとElapsedはどちらを使うべきか

TickElapsedのどちらを使うべきかは、使うTimerの種類と目的によります。

Windows FormsでUIを更新したいなら、System.Windows.Forms.TimerTickを使うのが基本です。

C#
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Tick += Timer_Tick;

バックグラウンド処理やUIに依存しない処理では、System.Timers.TimerElapsedを使うことがあります。

C#
System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += Timer_Elapsed;

ただし、ElapsedイベントからWindows FormsのUIを直接更新するのは避けるべきです。

UIを更新する場合はInvokeが必要です。

C#
label1.BeginInvoke(new Action(() =>
{
label1.Text = "更新";
}));

判断基準は次の通りです。

目的使うイベント
Windows FormsのUI更新Tick
WPFのUI更新DispatcherTimerTick
バックグラウンド定期処理Elapsedまたはコールバック
スレッドプールで軽量処理System.Threading.Timer

「C# Timer Tick」で調べている場合、Windows FormsのSystem.Windows.Forms.Timerを想定しているケースが多いです。

11. C# Timer Tickのベストプラクティス

11-1. 定期実行する処理はメソッドに分離する

Timer Tickで実行する処理は、イベントハンドラに直接書きすぎないようにしましょう。

悪い例です。

C#
private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
// 他にも処理が増えていく
}

処理が増えると、Tickイベントが読みにくくなります。

おすすめは、実際の処理をメソッドに分けることです。

C#
private void Timer_Tick(object sender, EventArgs e)
{
UpdateClock();
}

private void UpdateClock()
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

この形にすると、初回実行でも同じ処理を呼び出せます。

C#
UpdateClock();
timer.Start();

Timer Tickの処理は、イベントハンドラと実処理を分けるだけで、かなり保守しやすくなります。

11-2. UI更新と重い処理を分ける

Windows Forms TimerのTickイベントはUIスレッドで実行されます。

そのため、UI更新には向いていますが、重い処理には向いていません。

UI更新と重い処理は分けましょう。

C#
private async void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();

try
{
var data = await Task.Run(() => LoadData());
UpdateUi(data);
}
finally
{
timer.Start();
}
}

private void UpdateUi(string data)
{
label1.Text = data;
}

この例では、データ読み込みはバックグラウンドで行い、UI更新だけを分けています。

Timer Tickは「処理を開始するきっかけ」として使い、重い処理そのものをUIスレッドで実行しないことが重要です。

11-3. 例外処理を必ず入れる

Timer Tickは定期的に実行されるため、例外処理がないとトラブルに気づきにくくなります。

ファイル、ネットワーク、データベース、外部APIなどを扱う場合は、必ずtry-catchを入れましょう。

C#
private void Timer_Tick(object sender, EventArgs e)
{
try
{
CheckStatus();
}
catch (Exception ex)
{
labelStatus.Text = "エラーが発生しました";
Console.WriteLine(ex);
}
}

例外が発生したときにTimerを止めるか、継続するかも設計しておくと安全です。

C#
private void Timer_Tick(object sender, EventArgs e)
{
try
{
CheckStatus();
}
catch (Exception ex)
{
timer.Stop();
MessageBox.Show("Timer処理でエラーが発生しました: " + ex.Message);
}
}

定期処理では、同じエラーが何度も繰り返されることがあります。

そのため、ログ出力や停止判断も含めて考えることが大切です。

11-4. Timerの種類を用途に合わせて選ぶ

C#には複数のTimerがあります。

すべてを同じように使うのではなく、用途に合わせて選びましょう。

Windows Formsの画面更新なら、System.Windows.Forms.Timerがわかりやすいです。

C#
System.Windows.Forms.Timer

WPFなら、DispatcherTimerを使います。

C#
DispatcherTimer

バックグラウンド処理なら、System.Timers.TimerSystem.Threading.Timerを検討します。

C#
System.Timers.Timer
System.Threading.Timer

特に注意したいのは、UI更新の有無です。

UIを直接更新したいならUIスレッドで動くTimerを選びます。

バックグラウンドで処理したいなら、別スレッドで動くTimerを選びます。

Timerの種類を間違えると、Tickが動かない、UI更新で例外が出る、処理が重複するなどの問題が起きやすくなります。

11-5. 初回実行が必要な処理はTimer任せにしない

Timerは、Start直後に必ず即時実行されるものではありません。

Windows Forms TimerのTickは、通常、Interval経過後に初めて発生します。

そのため、初回実行が必要な処理は、Timer Tickを待たずに明示的に呼び出しましょう。

C#
UpdateData();
timer.Start();

このようにすることで、初回表示の遅れを防げます。

C#
private void Form1_Load(object sender, EventArgs e)
{
timer.Interval = 1000;
timer.Tick += Timer_Tick;

UpdateData(); // 初回実行
timer.Start(); // 以降は定期実行
}

private void Timer_Tick(object sender, EventArgs e)
{
UpdateData();
}

初回実行と定期実行を同じメソッドにまとめるのが、Timer Tickでは定番の実装です。

11-6. 長時間・高精度な処理にはTimer以外の選択肢も検討する

Timer Tickは便利ですが、高精度な時間制御には向いていません。

Windows Forms TimerはUIスレッドの状態に影響されます。

UIスレッドが忙しいと、Tickイベントのタイミングが遅れることがあります。

また、処理時間が長い場合、指定したInterval通りに実行されないこともあります。

高精度な計測が必要な場合は、Stopwatchを使って経過時間を測る方法があります。

C#
private Stopwatch stopwatch = new Stopwatch();

private void Start()
{
stopwatch.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = stopwatch.Elapsed.ToString();
}

この場合、Timerは画面更新のきっかけとして使い、正確な経過時間はStopwatchで取得します。

また、長時間実行されるバックグラウンド処理では、TimerよりもTaskasync/awaitBackgroundService、スケジューラーなどが適している場合もあります。

Timer Tickは便利な道具ですが、用途に合わない場面で無理に使わないことも大切です。

まとめ

C#のTimer Tickは、一定間隔で処理を実行したいときに便利な仕組みです。

特にWindows Formsでは、System.Windows.Forms.TimerTickイベントを使うことで、1秒ごとの時刻表示、カウントダウン、画面更新、状態チェックなどを簡単に実装できます。

基本形は次の通りです。

C#
private System.Windows.Forms.Timer timer;

private void Form1_Load(object sender, EventArgs e)
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}

Timer Tickが動かない場合は、まず次の点を確認しましょう。

Start()を呼んでいるか
Enabledがtrueになっているか
Intervalの単位をミリ秒として理解しているか
Tickイベントを登録しているか
Timerインスタンスをフィールドで保持しているか
Tick内で例外が発生していないか
UIスレッドを重い処理でブロックしていないか

また、Start直後に初回実行されないのは自然な動作です。

初回だけすぐ実行したい場合は、処理をメソッド化して、Timer開始前に手動で1回呼び出しましょう。

C#
UpdateData();
timer.Start();

C#には複数のTimerがあります。

Windows FormsのUI更新ならSystem.Windows.Forms.Timer、WPFならDispatcherTimer、バックグラウンド処理ならSystem.Timers.TimerSystem.Threading.Timerを選びます。

Timer Tickを安定して使うためには、Timerの種類、実行スレッド、Interval、初回実行、例外処理、停止・破棄のタイミングを理解することが重要です。

正しく使えば、C#のTimer Tickは画面更新や定期処理をシンプルに実装できる便利な機能です。