주기적으로 특정 행위를 반복하는 배치 프로그램이나 에이전트 프로그램을 구현할 때,
.NET의 Timer 클래스를 사용하면 편리하게 구현할 수 있다.
하지만 .NET에는 세 가지 Timer 클래스가 존재한다.
- System.Windows.Forms.Timer
- System.Timers.Timer
- System.Threading.Timer
각각 사용 방법도 다르고, 동작하는 개념의 차이도 있다.
만약 Timer 객체를 사용하고자 할 때는, 동작 원리를 이해하고 상황에 맞추어 선택할 수 있어야 한다.
1. Timer 클래스의 종류 및 동작 원리
1.1 System.Windows.Forms.Timer
- WinForms UI 스레드에서 실행되는 타이머로, 주로 UI 업데이트가 필요한 경우 사용
- UI스레드에서 실행되므로, UI 컨트롤을 간단하게 조작 가능
- Tick 이벤트 기반으로 동작
- 정확성이 상대적으로 낮아 배치 작업보다는 대개 UI 관련 이벤트 처리에 사용
정확성이 낮은 이유는 아래와 같다.
Winform은 기본적으로, UI 스레드만을 가진 싱글 스레드 구조임
> 만약 UI 이벤트 처리에 지연이 발생하면 더불어 Tick 이벤트 수행도 지연됨
> 반대로, Tick 이벤트에서 긴 작업을 수행하는 경우 UI가 멈출 수 있다.
>> 결과적으로, 안정적인 동작을 보장할 수 없다..! (Thread-safe 하지 않다)
예제 코드로 사용 방법을 살펴보자.
winform 의 Timer 클래스는 도구 상자로부터 손쉽게 생성할 수 있다.

Timer 객체를 생성했다면, 반복할 동작과 옵션을 설정한다.
using System.Diagnostics.Metrics;
namespace TesterWinform
{
public partial class Form1 : Form
{
int counter = 0;
public Form1()
{
InitializeComponent();
WinformTimertest();
}
private void WinformTimertest()
{
// 간격을 1초(1000밀리초)로 설정
timer1.Interval = 1000;
// Tick 이벤트 핸들러 추가
timer1.Tick += new EventHandler(Timer_Tick);
// Timer 시작
timer1.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
counter++;
this.Text = $"타이머 실행 중: {counter}초";
if (counter >= 10)
{
timer1.Stop();
MessageBox.Show("타이머가 10초 후 정지되었습니다.");
}
}
}
}
WinformTimerTest() 를 보면,,
위 코드는 '도구상자' 에서 사전에 클래스를 선언했다는 것을 기준이다.
만약 코드에서 객체를 생성하고 싶다면
timer1 = new System.Windows.Fomrs.Timer(); 로 생성하면 될 것이다.
1.2) System.Threading.Timer
주요 특징은 아래 두 가지이다.
ThreadPool 기반 : CLR의 스레드풀에서 자동적으로 관리된다.
백그라운드 실행 : UI 스레드와 완전히 분리된 스레드에서 실행
때문에, UI 작업과 분리되어 멀티쓰레딩 과 같은 작업은 가능해지지만,
만약 UI 작업을 하는 경우 기존 UI 스레드와의 충돌 가능성이 생기게 된다. (이 현상을 Cross-Thread 문제 라고 부른다.)
C# 에서는 이러한 상황을 방지하기 위해서, Invoke 라는 함수를 제공하고 있다.
만약 백그라운드 스레드에서 UI 작업을 하고자 한다면, Invoke 함수를 이용하여 신호를 받고 기다린 후 수행하는 것이다.
뿐만 아니라, 해당 타이머는 지역 변수로 선언되는 경우, C# 의 GC (가비지 컬렉터)에 의해서 수거될 수 있다.
때문에 지속적인 작업을 부여하는 경우에는 '클래스 필드'로 선언해야 안정적이다.
+ Dispose() 로 반드시 수동 해제를 해야 메모리 누수를 막는다.
어떻게 사용하는 지는 아래 코드로 살펴본다.
using System;
using System.Threading;
using System.Windows.Forms;
public class MainForm : Form
{
private System.Threading.Timer _timer;
private Label statusLabel;
private int counter = 0;
public MainForm()
{
InitializeComponents();
InitializeTimer();
}
private void InitializeComponents()
{
statusLabel = new Label
{
Text = "카운트: 0",
Location = new System.Drawing.Point(10, 10),
Width = 200
};
Controls.Add(statusLabel);
}
private void InitializeTimer()
{
_timer = new System.Threading.Timer(
callback: TimerCallback,
state: null,
dueTime: 0, // 즉시 시작
period: 1000); // 1초 간격
}
private void TimerCallback(object state)
{
counter++;
// UI 스레드로 마샬링 (크로스 스레드 방지)
if (statusLabel.InvokeRequired)
{
statusLabel.BeginInvoke((MethodInvoker)delegate
{
statusLabel.Text = $"카운트: {counter}";
});
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_timer?.Dispose(); // 리소스 정리
base.OnFormClosing(e);
}
}
1.2) System.Timers.Timer
주요 특징은 아래와 같다.
- AutoReset = false이면 한 번만 실행됨
- 이벤트 기반 (Elapsed 이벤트)
- 백그라운드에서 실행되지만, SynchronizingObject 설정 시 UI 스레드에서 실행 가능 (크로스 스레드 문제 해결!)
사실 기능은 Threading.Timer 와 유사하다.
그 이유는 사실 Win32 API의 TimerQueueTimer 를 기반으로 동작하기 때문이다.
하지만, Threading.Timer 와는 다르게 부가적인 기능이 많으므로 필요한 경우 선택하도록 한다.
아래 코드로 사용 방법을 살펴본다.
using System;
using System.Timers;
using System.Windows.Forms;
public class MainForm : Form
{
private System.Timers.Timer _timer;
private Label statusLabel;
private int counter = 0;
public MainForm()
{
InitializeComponents();
InitializeTimer();
}
private void InitializeComponents()
{
statusLabel = new Label
{
Text = "카운트: 0",
Location = new System.Drawing.Point(10, 10),
Width = 200
};
Controls.Add(statusLabel);
}
private void InitializeTimer()
{
// 타이머 인스턴스 생성 (1000ms 주기로 실행)
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += TimerElapsed;
_timer.AutoReset = true; // 반복 실행
// UI 스레드에서 이벤트 핸들링을 원할 경우, SynchronizingObject를 지정
_timer.SynchronizingObject = this;
_timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
counter++;
// SynchronizingObject가 지정되어 있으면 UI 스레드에서 호출된다.
statusLabel.Text = $"카운트: {counter}";
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_timer?.Stop();
_timer?.Dispose();
base.OnFormClosing(e);
}
}
Threading.Timer 와 Timers.Timer 를 한 눈에 비교해보자.
| 항목 |
System.Threading.Timer | System.Timers.Timer |
| 사용 방식 | 콜백 메서드(callback)를 사용하여 실행 | Elapsed 이벤트를 통해 이벤트 기반으로 동작 |
| 스레드 관리 | CLR의 스레드풀에서 동작하며, UI 스레드와 완전히 분리됨 | 기본적으로 스레드풀에서 동작하지만, SynchronizingObject를 설정하면 UI 스레드와 동기화 가능 |
| 크로스 스레드 처리 | UI 작업 시 Invoke 또는 BeginInvoke를 통해 크로스 스레드 이슈 해결 필요 | SynchronizingObject를 지정하면 자동으로 UI 스레드에서 처리 |
| 재시작 기능 | period 값을 지정하면 반복 실행 | AutoReset 프로퍼티를 통해 반복 여부를 설정 |
| 가비지 컬렉션 문제 | 지역 변수로 선언 시 가비지 컬렉터에 의해 수거될 수 있음 | 마찬가지로, 참조를 유지하지 않으면 수거될 수 있으므로, 필요시 클래스 필드로 선언 |
| 리소스 해제 | Dispose() 메서드를 명시적으로 호출해야 함 | Dispose() 메서드를 호출하여 리소스 해제를 수행해야 함 |
2. Timer 선택 기준
| Timer 클래스 | UI 스레드 | 백그라운드 스레드 | 실행정확도 | 사용 예 |
| System.Windows.Forms.Timer | O | X | 낮음 | Windows Forms UI 업데이트 |
| System.Timers.Timer | X | O | 중간 | 주기적인 백그라운드 작업 |
| System.Threading.Timer | X | O | 높음 | 고성능 타이머, 비동기 작업 |
3. REF
Timer 클래스 (System.Timers) | Microsoft Learn
Timer 클래스 (System.Timers)
반복 이벤트를 생성하는 옵션으로 설정된 간격 후 이벤트를 생성합니다.
learn.microsoft.com
C# Timer 차이점
원작자 : 소년포비 특정 작업을 주기적으로 실행하기 위해 흔히 Timer 객체를 사용합니다정해진 시간 간격으로 변수를 업데이트 한다던지, 모니터링 한다던지, 로그를 기록 한다던지, 그 작업 내
sourcenote.tistory.com
'Framework > .Net Framework' 카테고리의 다른 글
| [C#/Winforms] "응답 없음 (Not Responding)" 을 해결하는 방법 (0) | 2025.02.05 |
|---|