타이머 기능을 사용하게 되었다 꼭 타이머로 구현해야 하는 것은 아니지만 별도의 쓰레드로 동작하면서 특정 기능을 주기적으로 호출해야 하는 기능이 필요하게 되어 .Net에서 제공하는 타이머 기능을 조사했다 


목적 : 특정 작업을 주기적으로 실행 하기 위해 사용 


일단 동작하고 있는 프로세스 내부에서 기존의 작업과 별도로 동작해야 할 필요가 있는 경우 멀티 쓰레드 환경을 고려하게 된다 

두가지 조건을 만족해야 한다 

1. 다중 작업 처리 가능 

2. 특정 시간마다 이벤트 발생 


위 2가지를 고려하고 쓰레드 생성과 타이머 조사를 비교 했을 때 어느 것이 우위에 있는지 비교 분석을 하자면 

타이머의 장점 

 - Thread.Sleep 은 동작하는 프로세스에 인터럽트하는  강제로 프로세스를 멈추게 하는 성향이 있기 때문에 시스템 전체에 영향을 준다 (쓰레드의 단점) 

 - Thread를 만들어서 Sleep을 사용하지 않고 (Sleep을 사용하면 Timer 기능을 하는 Thread는 물론 시스템 전체가 멈춘다)

   Thread와 비교했을 때 반복(Interval) 간격을 옵션으로 간단하게 정할 수 있다 (2번 조건 만족)  

   시간 계산을 측정해서 구현할 수 있겠지만 Timer에서는 Interval을 설정만 하면 되기 때문에 사용하기 편리하다 (라이브러리 처럼 사용 가능) 

 - Timer는 Callback 개념으로 지정된 시간 후에 Callback을 하는 개념이라 상대적으로 부하가 적게 걸린다 ( 출처:http://nowonbun.tistory.com/139 )

   이 말은 정확한 증거는 아니지만 Thread에서는 Sleep()을 통해 다른 Thread로 옮겨가게 되지만 예를 들면 Sleep(10) 과 같이 작은 수치의 밀리세컨드를 지정하더라도 성능에 영향을 미치게 되지만 CallBack 함수로 호출되는 Timer의 경우는 상대적으로 부하가 적게 걸린다는 말로 보인다.  


타이머라는 것은 기본적으로 시간의 흐름을 연속적으로 측정하기 위함이기 때문에 굳이 Thread로 만들지 않더라도 .NET이 제공해주는 라이브러리 클래스를 사용하면 간단하고 편리하게 구현할 수 있다 (Thread Pool 관리도 자동으로 해준다는?? 부분이 있다)  


.NET 프레임워크에서 제공하는 타이머에는 3가지가 있다 

1. System.Windows.Forms.Timer 

2. System.Threading.Timer

3. System.Timers.Timer


MSDN + 검색한 내용의 간단한 설명이다 (출처: http://blog.daum.net/starkcb/117)


1) System.Windows.Forms.Timer

사용자가 정의한 간격마다 이벤트를 발생시키는 타이머를 구현합니다. 

이 타이머는 Windows Forms 응용 프로그램에서 사용할 수 있도록 최적화되었으며 창에서 사용해야 합니다


윈도우 폼 타이머라고 한다 윈도우 응용프로그램과 동일한 Thread에서 동작한다

즉, Timer는 UI Thread 상에서 동작한다 - 즉 Mutil Thread 환경이 아니라 단일 Thread 환경이다

반복주기를 설정하고 반복 주기마다 실행되는 이벤트를 등록한다


2) System.Threading.Timer


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


쓰레드 타이머라고 한다

UI Thread와 Work Thread로 나뉘어 진다 

UI Thread - 평소에는 유후 상태, 기다리다가 메시지 들어오면 메시지 루프에서 메시지 처리 (MFC가 기억난다...) 

Work Thread - background 작업, 메시지 루프는 사용하지 않는다 

멀티쓰레드로 두 쓰레드는 독립적으로 수행된다 

닷넷의 ThreadPool에서 관리한다 

이벤트 대신 Callback 매서드를 사용한다 

만약 반복 실행 작업이 UI Thread와 관련이 생기면 Cross Thread 문제가 발생한다 (Invoke, BeginInvoke를 통해 핸들링할 수 있다) 

메시지가 스레드에서 펌프되지 않을 경우에 유용

- 이 말은 Windows Form Timer의 경우 Message Loop에서 UI의 특정 이벤트에 반응하는 방식이 아니라 UI Thread와는 별개로 Background로 작업이 진행 될 때 즉 Work Thread로 사용될 때 유용하다는 말이다

  

3) System.Timers.Timer


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


서버타이머라고 한다 

Synchronizing Object 속성에 따라 달라진다 

Synchronizing Object 속성을 form 객체로 하면 UI Thread 상에서 동작 

Synchronizing Object 속성을 지정하지 않으면 Work Thread 상에서 동작 

Multi Thread 환경에서 Work Thread를 구현해서 사용 

시스템 시간(서버)을 사용해서 프로그램 리소스(시간)를 사용하는 System.Thrading.Timer 보다 정확

Thread 사이를 이동하면서 발생한 이벤트를 처리 가능 

주기마다 실행되는 이벤트를 등록하고 쓰레드 타이머와 마찬가지로 UI Thread를 핸들링 하기위해서는 Invoke, BeginInvoke를 이용해야 한다 

코드형태는 윈도우 폼 타이머와 비슷하다 


.NET의 타이머를 보다 보면 UI Thread와 Work Thread 간 크로싱 에러나 별도의 동작하는 Work Thread 얘기가 나오는데 간단히 생각하면 싱글 쓰레드인지 멀티 쓰레드인지 생각하면 된다 UI Thread에서만 동작하길 원하면 Windows.Forms.Timer를 사용하면 되고 멀티쓰레드 환경에서 독립적으로 동작하길 원한다면 System.Threading.Timer나 System.Timers.Timer 를 사용하면 된다 더 자세한 내용은 아래서 이어 가겠다 


두가지 측면에서 더 살펴보자면 

1. 사용법상의 차이점 

3가지 방법의 간단한 사용법을 쓰면 


1. System.Windows.Forms.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();


Windows.Forms.Timer 를 제외하고는 UI Thread에서 만들어진 컨트롤에 접근하려면 

Cross Thread 문제가 있으므로 마샬링?? 된 호출(Invoke / BeginInvoke)를 이용해야 하는 차이점이 있다고 한다??


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

먼저 UI Thread 라는 것의 환경에 대해 알아야 한다. 윈도우 응용프로그램 중 예를 들면 Button이나 ListView나 ComboBox 등 각종 컨트롤이 생성되고 핸들링 되는 것은 UI Thread 안에서 동작하게 된다 


이와 별도로 Work Thread라는 것이 있는데 기본(Default Thread) 이외에 개발자가 별도의 쓰레드를 생성해서 작업을 실행하면 이것을 Work Thread라고 한다. 또한 UI Thread 입장에서는 .NET의 Thread Pool에 의해 실행되는 쓰레드도 Work Thread로 볼 수 있다고 한다 

쓰레드가 다르면 쓰레드의 고유번호가 다르기 때문에 

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


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

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

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

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 안에서 Timre가 동작한다고 짐작할 수있다 즉 멀티 쓰레드 환경이 아닌 싱글 쓰레드라고 생각되는 점이다... 

그러면 궁금한점... 싱글 쓰레드면 왜 Windows.Forms.Timer를 쓰는 것인지?? 이해가 어렵다.. Timer는 기본 적으로 특정 작업에 특화되려면 Background에서 동작하는게 필수라고 생각했는데 왜 Single Thread로 만들었는지 의외였고 UI 단일 Thread에서는 Timer가 Message Loop에서 동작했다 아마도 UI에서만 Thread 변경없이 Message(화면 Event)가 들어오면 반응하도록 설계하지 않았을가 싶다. 


2. System.Threading.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가 분리되어 있다 

이 두 쓰레드는 독립적으로 수행되기 때문에 멀티 스레드의 환경을 가진다 앞의 Forms.Timer 객체와는 달리 Callback 매서드에서 시간이 오래 걸리는 작업을 수행해도 프로그램이 대기상태에 빠지지 않는다 

참고로 이 Timer는 닷넷의 Thread Pool 에서 관리한다 


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

위에서 한번 적었지만 이 Timer는 UI Thread에서 수행될 수도 있고 Work Thread에서 수행 될 수도 있다고 한다 Synchronizing Object 속성을 Form으로 하면 UI 이고 지정하지 않으면 Work 이다  


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

timer.SynchronizingObject = this;

 

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

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

를 확인해 보세요


중요한 것은 Timer Thread를 어디에서 동작할 것인가 이다 그리고 Work Thread에서 UI Thread 전환 시 크로스 쓰레드 문제가 발생하기 때문에 Invoke와 같은 코드로 극복이 가능하나 Invoke는 강제 동기화 이기 때문에 시스템 저하를 가져올 수 있다 결국 UI Thread에서는 Forms.Thread를 사용해야 Invoke로 인한 성능저하가 없다는 것이다. 

그렇다면? 멀티 쓰레드로 동작하지 않는 것과 Invoke를 사용해서 강제 동기화를 하면서 다중 작업처리를 하는 것의 차이는 시스템이 복잡할 경우 테스트나 비교를 정확히 해봐야 알 것 같다 

 

2번 Threading.Timer와 Timers.Timer의 공통점은 멀티 쓰레드로 동작할 수 있다는 것이다 

그렇다면 차이점은 무엇일까? 

일단 Threading.Timer는 Work Thread에서만 동작이 가능하지만 Timers.Timer는 UI or Work 모두가 가능하다 

그리고 Therading.Timer는 프로그램 리소스 즉 프로세스 자체에 인터럽트가 걸리면 같이 멈추거나 느려지지만 Timers.Timer는 시스템 시간(OS??)에 영향을 받기 때문에 프로그램이 느려져도 TImer 스레드는 시간에 맞추어 실행이 된다 


하나의 예를 들어보자면 이 글작성 당시 듀랑고라는 넥슨 게임이 오픈 초기라 대기열에 따른 부하에 의해 게임 시작이 어렵다 즉 app 자체가 느려짐에 따라 다른 기능들도 모두 영향을 받는다면 타이머도 같이 느려져야 하는지 아니면 타이머는 정상적으로 체크를 해야하는지에 대한 고민이 생긴다. 

일단 나의 결론은 타이머 성격의 작업은 리얼타임의 일정한 시간에 이루어 져야 한다는 것이다. 만약 대기에 의해 서버를 늘리거나 외부 환경이 변경되었을 때 그것을 타이머로 체크한다면 app 자체의 시간이나 속도와는 상관없이 변경된 환경을 체크해야 할 것이다. 이런 경우라면 Threading.Timer가 아니라 Timers.Timer를 적용해서 작업자체에 대한 주기성을 지키는 것이 더 빨리 대처하는 타이머라고 생각된다. 


----------------------------------------------------------------------------------------------------------------- 참고 자료 (http://blog.daum.net/starkcb/117)

타 차이점 및 요약, 참조

아래 표는 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 속성을 사용하여 실행됩니다. 
스레드 타이머의 간격은 Timer 생성자에서 설정됩니다. 



스레드에서 타이머를 다루는 방식을 보면 알 수 있듯이 각 타이머의 용도는 서로 다릅니다.

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

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

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

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


추가로 몇몇 자료들을 더 보면 Threading.Timer 보다 Timers.Timer가 더 Thread Safe 하다고 되어 있다 

그 이유는 SynchronizingObject 를 통해 Thread를 제어할 수 있는것이 더 safe 해서 인지 짐작만 된다 


추가로 더 조사한 내용을 올리면 아래 링크에서  

https://stackoverflow.com/questions/1416803/system-timers-timer-vs-system-threading-timer


CLR Via C#", Jeff Ritcher 는 그의 저서에서 System.Timers.Timer의 경우 비추천했다는 말이 나온다 UI와 연관되어 사용하는 것이라는 이유라던데 사무실에 있는 책같은데 한번 확인해 봐야겠다 그리고 추가적으로 .Net Core에서는 사라졌기 때문에 Work Thread로 사용하려면 결국 System.Threading.Timer를 사용해야 한다는 이유가 된다. 위의 Thread Safe 하다는 증거를 찾지 못한다면 System.Timers.Timer 의 장점은 사라져 보인다.    


추가로 .Net 타이머에 대해 비교한 내용이다 한번 읽어 보자 

https://web.archive.org/web/20150329101415/https://msdn.microsoft.com/en-us/magazine/cc164015.aspx


http://robertgreiner.com/2010/06/using-stopwatches-and-timers-in-net/



'Computer Language > C#|.Net' 카테고리의 다른 글

이스케이프 문자 처리 법  (0) 2018.01.06

별건 아니다 

이스케이프 문자를 처리하는 방법 중에 간단한 법이 있는데 간단히 말하면 ignore 시키는 것이다 


//  \\ : 백슬래시(\) 기초 하나를 표현 

// 한글입력기에서는 : \ 이지만 영문입력기에서는 슬래시(/)의 반대 

Console.WriteLine(" \\ : 백슬래시 표현 ");

      

// 백슬래시 다음에 나오는 문자는 이스케이프 문자로 본다.

// 그래서 두개를 붙여서 \를 표현한다 

Console.WriteLine(" C:\\Home\\MyRoom\\Default.cs");

      

// @를 붙이면 자동으로 이스케이프 문자를 무시한다 그래서 원래 쓰던 방식으로 써도 문제가 없다 

// 파이썬에서는 정규식을 쓸때 r을 붙여서 이스케이프를 무시한다 패턴에 /를 사용하는데 이스케이프 때문에 //를 매번 붙이면 복잡해진다

// r은 Row String이라는 규칙을 말한다. 

// 닷넷의 정규식에도 Ignore Escape 가 있다 

// 어쨌든 특정 문자 하나를 처리하기 위해 Escape On/Off 방식으로 처리한다는 점이 중요하다

Console.WriteLine(@"C:\Home\MyRoom\Default.cs");


      

// 문자열 앞에 @ 기호를 붙이면 문자열 자체로 본다.

// 그래서 아래와 같이 공백이 붙으면 공백도 문자의 일부로 보고 처리하니 아래와 같은 점은 주의하자

Console.WriteLine(

  @" 

       C:\Home\MyRoom\Default.cs

   ");


간단한거 하나 남기려 해도 쉽지않다... 블로그에 올리는 방식을 좀 고민해 봐야겠다  

초안만 작성된게 수십장인데.. ㅋㅋ 

어쨌든.. 결과는 아래와 같이 나온다 


'Computer Language > C#|.Net' 카테고리의 다른 글

.Net Timer Class 비교(3가지) 1  (0) 2018.01.29

+ Recent posts