소년포비의 세계정복!!

[C#] 세가지 Timer 와 그 차이점 본문

프로그램 세상/C#

[C#] 세가지 Timer 와 그 차이점

소년포비 2009. 10. 14. 09:39

 

특정 작업을 주기적으로 실행하기 위해 흔히 Timer 객체를 사용합니다

정해진 시간 간격으로 변수를 업데이트 한다던지, 모니터링 한다던지, 로그를 기록 한다던지, 그 작업 내용은 무궁무긴 하겠죠

Timer 객체는 이러한 주기적 작업을 아주 쉽게 처리해 주는, 닷넷 프레임워크에서 제공하는 고마운 객체입니다


그러나 한가지 생각해 볼 문제가 있네요..

닷넷 프레임워크에는 무려 3가지 서로 다른 Timer 를 제공하고 있다는 겁니다. 바로 아래 3가지 Timer 입니다
1. System.WIndows.Forms.Timer
2. System.Threading.Timer

3. System.Timers.Timer

 

닷넷이 이 3가지 Timer 를 각각 제공하는 이유가 무엇일까요?

필자는 이 문제(?)에 대해, 몇 년전에 의구심을 가졌읍니다만, 당시 의구심만 가진채 그냥 세월을 보내 버렸습니다 --;

그리고는 대략 아는 지식으로 대략 적절 할 것 같은(?) Timer을 사용해 왔던 것 같습니다

게을렀던 거죠. 의구심이 들면 파고들어 정복하는 사람이 성공합니다. ㅋㅋ , 기술이든 인생이든...

예기가 다른 길로 세네요.. ㅎ,

 

우선 이 세가지 서로다른 Timer 의 msdn 설명을 볼까요


1) System.Windows.Forms.Timer
사용자가 정의한 간격마다 이벤트를 발생시키는 타이머를 구현합니다. 이 타이머는 Windows Forms 응용 프로그램에서

사용할 수 있도록 최적화되었으며 창에서 사용해야 합니다

2) System.Threading.Timer

지정된 간격으로 메서드를 실행하는 메커니즘을 제공합니다

 

3) System.Timers.Timer

응용 프로그램에 되풀이 이벤트를 생성합니다

 


msdn 설명을 봐도,
'System.WIndows.Forms.Timer 가 윈도우 응용프로그램에 최적화 되었다' 라는 말 빼고는 거의 차이점을 느낄 수 없네요

 

물론 msdn은 보다 상세한 내용을 더 기술되어 있습니다만, 이 글에서는 이 세가지 Timer 의 차이점을 크게 두 가지 측면에서
살펴 볼까 합니다

1. 사용법상의 차이점

2. 수행되는 Thread 환경의 차이점

 

* 사용법의 차이

먼저 사용법의 차이를 알아보죠

사용법의 차이는 말 그대로 사용법입니다. 이것이 원리는 아니죠.
원리가 다르기 때문에 사용법이 다른 것이지, 사용법이 다르기 때문에 원리가 다른건 아닙니다

그럼에도, 사용법 차이점부터 알아 보는 것은.......... 쉽기 때문이죠 ^^;

(개발자 여러분, 사용법만 익히지 말고 원리를 익힙시다)

 

1. System.Windows.Forms.Timer 사용법
윈도우 응용프로그램 개발자들에겐 아마 가장 익숙한 Timer 일 것입니다

- 객체 생성

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

- 반복 주기 및 작업 설정

timer.Interval = 1000; //주기 설정

timer.Tick += new EventHandler(timer_Tick); //주기마다 실행되는 이벤트 등록

void tmrWindowsFormsTimer_Tick(object sender, System.EventArgs e)
{
      //수행해야할 작업

}

- Timer 시작

timer.Enable = true 또는 timer.Start();

- Timer 중지

timer.Enable = false 또는 timer.Stop();

 

 

2. System.Threading.Timer 사용법

- 객체 생성

Timer 객체를 생성할 때, 반복적으로 실행하게 될 메서드를 콜백 메서드로 등록해야 합니다

System.Threading.Timer timer = new System.Threading.Timer(CallBack);

- 반복 주기 및 작업 설정

이 Timer 에는 Change 메서드가 있는데, 이 메서드는 dueTime과 period 를 입력받습니다

dueTime은 Timer 가 시작하기 전 대기(지연)시간이며 period는 반복 주기입니다
timer.Change(0, 1000);

그리고 반복 실행 작업이,
윈도우 응용프로그램의 UI Thread와 연관된다면, Cross Thread 문제가 발생하기 때문에 Invoke나 BeginInvoke를

통해 핸들링 해야 합니다.

앞서, Timer 객세 생성시 등록한 콜백 메서드에서 BeginInvoke를 통해 UI 쓰레드를 핸들링 할 수 있습니다

 

delegate void TimerEventFiredDelegate();

void CallBack(Object state)
{
    BeginInvoke(new TimerEventFiredDelegate(Work));
}
        
private void Work()
{

     //수행해야할 작업(UI Thread 핸들링 가능)
}

 

- Timer 시작

위의 Change 메서드의 dueTime 이 0 이므로 그 즉시 시작된다. Start와 같은 별도의 시작 명령이 존재하지 않음

- Timer 중지
timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);

dueTime와 period 를 무한대로 잡아서 Timer 가 실행되지 않도록 하는 것이 중지하는 것과 같습니다

 

 

3. System.Timers.Timer 사용법

- 객체 생성

System.Timers.Timer timer = new System.Timers.Timer();

 

- 반복 주기 및 작업 설정
timer.Interval = 1000;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);  //주기마다 실행되는 이벤트 등록


이 Timer 역시 UI Thread를 핸들링 하기 위해서 Invoke 나 BeginInvoke를 이용해야 합니다
delegate void TimerEventFiredDelegate();
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    BeginInvoke(new TimerEventFiredDelegate(Work));            
}

private void Work()
{

     //수행해야할 작업(UI Thread 핸들링 가능)
}

 

- Timer 시작

timer.Enable = true 또는 timer.Start();

- Timer 중지

timer.Enable = false 또는 timer.Stop();

 

결론적으로 보면,

Timer 객체의 사용법 자체는 그리 어렵지 않습니다. 또한 세 가지 Timer 의 사용법은 대동소이 함을 알 수 있습니다

다만 윈도위 응용프로그램에서 Timer 를 사용할 때 System.WIndows.Forms.Timer 를 제외하고는

UI Thread 에서 만들어진 컨트롤에 접근하려면 크로스 쓰레드 문제가 있으므로 마샬링 된 호출(Invoke / BeginInvoke) 를

이용해야 하는 차이점이 있습니다

msdn의 설명처럼 System.Windows.Forms.Timer 는 윈도우 응용프로그램에 최적화 되어 있나 보네요..

 

그럼 왜 System.Windows.Forms.Timer 는 크로스쓰레드 문제가 발생하지 않을까요?

그리고 정말 사용법 처럼 크게 차이가 나지 않는 걸까요?


다음에 설명할, 두번째 관점인 '수행되는 Thread 환경의 차이점'에서 이를 알아보도록 하죠

 

 

 

* 수행되는 쓰레드(Thread) 환경의 차이

앞서 사용법에서 UI Thread 라는 말을 했습니다

윈도우 응용프로그램을 예로 들어, 버턴이나 각종 컨트롤이 생성되고 핸들링 되는 것은 UI Thread 상에서 이루어집니다

 

이와 다른 개념이 Work Thread 인데요, 기본 쓰레드(Default Thread) 이외에
개발자가 별도의 쓰레드를 생성하여 작업을 실행한다면 이는 Work Thread(작업자 쓰레드) 라 합니다

또한 UI Thread 입장에서는, 닷넷의 ThreadPool 에 의해 실행되는 쓰레드도 Work Thread 로 볼 수 있습니다

 

쓰레드가 다르다면 쓰레드의 고유번호도 당연히 다릅니다

System.Threading.Thread.CurrentThread.IsThreadPoolThread 속성은 현재 쓰레드의 고유 식별자 값을 가져 옵니다

우린 이 속성을 통해 Timer 객체가 수행되는 쓰레드를 알아 보도록 하겠습니다

 

1. System.Windows.Forms.Timer 의 쓰레드 환경

윈도우 응용프로그램에 최적회 되어 있다는 이 Timer 는 윈도우 응용프로그램 기본 쓰레드와 동일한 쓰레드 상에서 동작합니다

이를 확인하기 위해, 다음과 같이 코드 중간에 IsThreadPoolThread  속성을 확인해 봅니다

 

- 기본 쓰레드의 고유 번호를 확인한다

윈도우응용프로그램 생성자나 기타 이벤트에서 아래 코드를 기입합니다

MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

 

- Timer 쓰레드의 고유번호를 확인한다

Timer 의 Tick 이벤트에서 다음의 코드를 기입합니다

void timer1_Tick(object sender, EventArgs e)
{
     MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

     //수행해야할 작업
}

 

이렇게 확인 해 보면 두 쓰레드는 동일한 고유번호를 반환하게 됩니다

이 말은 곧, 윈도우응용프로가램의 기본 쓰레드인 UI Thread 상에서 Timer이 동작한다는 것을 짐작할 수 있습니다

즉 멀티 쓰레드 환경이 아닌 것이죠

예로, Tick 이벤트에 시간이 긴~ 작업이 수행된다면 프로그램은 그 시간 동안 블럭 된 대기 한다는 것입니다

Timer 의 동작이 기본 프로그램 동작과 독립적으로 수행된다고 생각하시면 안됩니다

 

2. System.Threading.Timer 의 쓰레드 환경

역시 앞서와 같이 기본 쓰레드와 Timer 쓰레드의 고유번호를 확인 해 봅니다

 

- 기본 쓰레드의 고유 번호를 확인한다

윈도우응용프로그램 생성자나 기타 이벤트에서 아래 코드를 기입합니다

MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

 

- Timer 쓰레드의 고유번호를 확인한다

CallBack 메서드에서 다음과 같이 코드를 기입합니다

void CallBack(Object state)
{
      MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

      BeginInvoke(new TimerEventFiredDelegate(Work));
 }

 

어떻습니까? 두 쓰레드는 다른 고유번호를 반환하지요

즉 UI Thread 와 다른 쓰레드, 즉 Work Thread(작업자 쓰레드)임을 알 수 있습니다

이는 곧 멀티 쓰레드가 된 셈이죠. 두 쓰레드는 서로 독립적으로 수행될 것입니다

앞서, System.Windows.Forms.Timer 객체와는 달리 CallBack 메서드에 시간이 오래 걸리는 작업을 수행해도

프로그램이 대기상태로 빠지는 않죠. Timer 동작이 기본 프로그램의 동작과는 독립적으로 수행되는 것이죠.

 

참고로 이 Timer 는 닷넷의 ThreadPool(쓰레드풀) 에서 관리합니다

 

3. System.Timers.Timer 의 쓰레드 환경

결론부터 말하자만, 이 Timer 는 기본 쓰레드에서 수행될 수도 있고, 작업자 쓰레드에서 수행될 수도 있습니다

만일, SynchronizingObject 속성을 폼객체로 한다면 Timer는 UI 쓰레드 상에서 동작할 것이며

이 속성을 지정하지 않는다면, 작업자 쓰레드 상에서 동작하게 됩니다

 

아래와 같이 SynchronizingObject 속성의 설정 여부에 따른 ManagedThreadid 값을 확인해 보기 바랍니다

timer.SynchronizingObject = this;

 

타이머 쓰레드의 고유번호를 알기 위해 Elapsed 이벤트에

MessageBox.Show(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

를 확인해 보세요

 

---

결국 Timer 의 실행이 기본 쓰레드에서 하느냐, 작업자 쓰레드 에서 하느냐에 차이인데요,

앞서, 사용법의 차이를 살펴 봤을 때 System.Windows.Forms.Timer 객체를 제외하고는 윈도우응용프로그램의 UI 컨트롤

핸들링 시 크로스 도메인 문제가 발생했던 원인이 되는 것입니다

 

 

 

* 기타 차이점 및 요약, 참조

아래 표는 msdn magazine에 소개된 세 Timer 의 차이점에 대한 표입니다

우리가 알아 본 내용 이외에도, 쓰레드 안정성(동기화 문제)에 대한 내용도 있습니다

 

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 

System.Windows.Forms

System.Timers

System.Threading

Timer event runs on what thread?

UI thread

UI or worker thread

Worker thread

Instances are thread safe?

No

Yes

No

Familiar/intuitive object model?

Yes

Yes

No

Requires Windows Forms?

Yes

No

No

Metronome-quality beat?

No

Yes*

Yes*

Timer event supports state object?

No

No

Yes

Initial timer event can be scheduled?

No

No

Yes

Class supports inheritance?

Yes

Yes

No

* Depending on the availability of system resources (for example, worker threads)

 

 

마지막으로 msdn의 설명을 옮기며 마칩니다

 

서버 타이머, Windows 타이머 및 스레드 타이머

Visual Studio 및 .NET Framework에는 세 개의 타이머 컨트롤 즉, 도구 상자구성 요소 탭에서 볼 수 있는 서버 기반 타이머, 도구 상자Windows Forms 탭에서 볼 수 있는 표준 Windows 기반 타이머 및 프로그래밍 방식으로만 사용할 수 있는 스레드 타이머가 있습니다.

Windows 기반 타이머는 Visual Basic 1.0 이상의 버전에 있으며 지금까지 크게 변경되지 않았습니다.
이 타이머는 Windows Forms 응용 프로그램에서 사용하도록 최적화되어 있습니다.
서버 기반 타이머는 일반 타이머를 서버 환경에서 최적으로 실행되도록 업데이트한 것입니다.

스레드 타이머는 이벤트 대신 콜백 메서드를 사용하는 간단한 소형 타이머로서 스레드 풀 스레드에서 제공합니다.

Win32 아키텍처에는 UI 스레드와 작업자 스레드라는 두 종류의 스레드가 있습니다.
UI 스레드는 대부분의 시간을 유휴 상태로 보내며 메시지 루프에 메시지가 도착할 때까지 기다립니다. 메시지가 도착하면
이 메시지를 처리하고 다음 메시지가 도착할 때까지 기다립니다. 이에 비해 작업자 스레드는 백그라운드 처리를 수행하는 데 사용하며 메시지 루프를 사용하지 않습니다.

Windows 타이머와 서버 기반 타이머는 모두 Interval 속성을 사용하여 실행됩니다.
스레드 타이머의 간격은 <?XML:NAMESPACE PREFIX = MSHelp NS = "http://msdn.microsoft.com/mshelp" />Timer 생성자에서 설정됩니다.
스레드에서 타이머를 다루는 방식을 보면 알 수 있듯이 각 타이머의 용도는 서로 다릅니다.

  • Windows 타이머는 UI 스레드가 프로세싱을 수행하는 데 사용하는 단일 스레드 환경을 위해 설계되었습니다. Windows 타이머의 정확도는 55밀리초로 제한되어 있습니다. 이 일반 타이머는 사용자 코드에서 사용할 수 있는
    UI 메시지 펌프가 필요하며 항상 동일한 스레드에서 실행되거나 다른 스레드로 마샬링됩니다.
    이 기능은 COM 구성 요소의 성능을 저하시킵니다.

  • 서버 기반 타이머다중 스레드 환경에서 작업자 스레드와 함께 사용하도록 설계되었습니다. 두 스레드는 서로 다른 아키텍처를 사용하므로 서버 기반 타이머가 Windows 타이머보다 정확합니다.
    서버 타이머는 스레드 사이를 이동하면서 발생한 이벤트를 처리할 수 있습니다.

  • 스레드 타이머는 메시지가 스레드에서 펌프되지 않는 경우에 유용합니다.
    예를 들어, Windows 기반 타이머는 운영 체제의 타이머 지원 기능에 의존하며 스레드에서 메시지를 펌프하지 않을 경우에는 타이머 관련 이벤트가 발생하지 않습니다. 이 경우에는 스레드 타이머가 보다 더 유용합니다.

Windows 타이머는 System.Windows.Forms 네임스페이스에, 서버 타이머는 System.Timers 네임스페이스에 그리고 스레드 타이머는 System.Threading 네임스페이스에 있습니다.