Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 5개 있습니다.)
.NET Framework: 486. Java의 ScheduledExecutorService에 대응하는 C#의 System.Threading.Timer
; https://www.sysnet.pe.kr/2/0/1823

VC++: 103. C++ CreateTimerQueue, CreateTimerQueueTimer 예제 코드
; https://www.sysnet.pe.kr/2/0/11090

.NET Framework: 636. System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점
; https://www.sysnet.pe.kr/2/0/11134

Windows: 189. WM_TIMER의 동작 방식 개요
; https://www.sysnet.pe.kr/2/0/12539

.NET Framework: 2019. C# - .NET에서 제공하는 3가지 Timer 비교
; https://www.sysnet.pe.kr/2/0/13069




System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점

이전에도 설명했지만, System.Threading.Timer는 그 기반을 Win32 API인 CreateTimerQueueTimer 함수에 두고 있습니다.

C++ CreateTimerQueue, CreateTimerQueueTimer 예제 코드
; https://www.sysnet.pe.kr/2/0/11090

이 때문에 GC가 있긴 해도 System.Threading.Timer는 IDisposable을 구현하고 있으므로 가능한 Dispose를 명시적으로 불러주는 게 자원 관리상 좋습니다. 결국, 다음과 같이 코딩할 수 있는데요.

using System;

class Program
{
    static void Main(string[] args)
    {
        new WorkItem(5, 0);
        Console.ReadLine();
    }

    class WorkItem
    {
        public int Arg;
        System.Threading.Timer _timer;

        public WorkItem(int arg, int interval)
        {
            Arg = arg;
            _timer = new System.Threading.Timer(timerCallback, this, interval, 0);
        }

        private static void timerCallback(object state)
        {
            WorkItem workItem = state as WorkItem;

            if (workItem == null)
            {
                return;
            }

            if (workItem._timer == null)
            {
#if DEBUG
                throw new ApplicationException("workItem._timer == null");
#else
                return;
#endif
            }

            workItem._timer.Dispose();
        }
    }
}

위의 코드는 C++에서 설명했던 것과 유사한 문제에 빠집니다. 즉, _timer 변수에 값이 들어가는 것과, "new System.Threading.Timer"로 인해 타이머 대기 작업이 큐에 들어가 실행되는 시점이 차이가 발생한다는 점입니다. 경우에 따라, timerCallback 메서드가 실행되는 시점에 아직 _timer 변수가 초기화되지 않는 경우가 있습니다. (실제로, 제가 만든 응용 프로그램에서 아주 가끔씩 발생합니다.)

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




첨언하자면, 이 글의 예제에서 사용한 타이머 대기 작업 사용 방법은,

new System.Threading.Timer(timerCallback, this, 0, 0);

결국 다음의 작업과 동일한 것입니다.

ThreadPool.QueueUserWorkItem(workFunc, 6);

static void workFunc(object state)
{
    Console.WriteLine(state);
}

따라서 System.Threading.Timer의 마지막 2개의 인자에 0, 0을 지정하는 것이라면 ThreadPool.QueueUserWorkItem을 사용하는 것이 더 안전합니다.

하지만, 그 외의 경우라면 어떻게 해서든 안전하게 실행시켜야 할 텐데요. 그때는 어쩔 수 없을 것 같습니다. 다음과 같은 식으로 약간의 조악한 안전장치를 넣어두어야겠지요.

private static void timerCallback(object state)
{
    WorkItem workItem = state as WorkItem;

    try
    {
        if (workItem == null)
        {
            return;
        }

        int tryCount = 5;
        while (workItem._timer == null && tryCount-- > 0)
        {
            Thread.Sleep(10);
        }

        if (workItem._timer == null)
        {
#if DEBUG
                throw new ApplicationException("workItem._timer == null");
#else
                return;
#endif
        }

        Console.WriteLine(workItem.Arg);

        workItem._timer.Dispose();
    }
    catch
    {
        return;
    }
}

혹시 더 좋은 의견 있으신 분은 덧글 부탁드립니다. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 1/3/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2017-01-21 08시53분
[spowner] timer 필드에 Timer 인스턴스가 들어가기 전에 timerCallback 메소드가 호출되는 경험을 하셨다는 것은 좀 놀랍네요... 덕분에 그럴 수 있다는 것을 지금에야 자각했습니다.

만약 timer 인스턴스를 생성하는 곳과 timerCallback을 처리하는 것이 WorkItem에서 이루어지는 것이라면, ResetEvent를 사용하는게 while-Sleep 보다는 낫지 않을까 하는 의견 내봅니다
[guest]
2017-01-21 08시54분
[spowner] 또는 lock도 괜찮을 것 같습니다
[guest]
2017-01-21 11시04분
[ryujh] 안녕하세요. 생성자에서 interval과 0 를 지정해야 하는지는 모르겠지만
아래의 코드로 바꾸면 되지 않을까하는 제 생각입니다.

수정 전
            _timer = new System.Threading.Timer(timerCallback, this, interval, 0);
수정 후
            _timer = new System.Threading.Timer(timerCallback, this, Timeout.Infinite, Timeout.Infinite);
            _timer.Change(interval, 0);
[guest]
2017-01-21 11시30분
[spowner] @ryujh !! ^^
[guest]
2017-01-22 03시07분
오... ryujh님 의견대로 Change하는 것이 더 부드럽겠습니다. ^^
정성태

... 91  92  93  [94]  95  96  97  98  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11584정성태7/5/201818249Math: 35. GeoGebra 기하 (12) - 삼각형의 내심과 내접하는 원파일 다운로드1
11583정성태7/5/201818053.NET Framework: 785. public으로 노출되지 않은 다른 어셈블리의 delegate 인스턴스를 Reflection으로 생성하는 방법파일 다운로드1
11582정성태7/5/201824654.NET Framework: 784. C# - 제네릭 인자를 가진 타입을 생성하는 방법 [1]파일 다운로드1
11581정성태7/4/201821382Math: 34. GeoGebra 기하 (11) - 3대 작도 불능 문제의 하나인 임의 각의 3등분파일 다운로드1
11580정성태7/4/201818163Math: 33. GeoGebra 기하 (10) - 직각의 3등분파일 다운로드1
11579정성태7/4/201817244Math: 32. GeoGebra 기하 (9) - 임의의 선분을 한 변으로 갖는 정삼각형파일 다운로드1
11578정성태7/3/201817394Math: 31. GeoGebra 기하 (8) - 호(Arc)의 이등분파일 다운로드1
11577정성태7/3/201817338Math: 30. GeoGebra 기하 (7) - 각의 이등분파일 다운로드1
11576정성태7/3/201819525Math: 29. GeoGebra 기하 (6) - 대수의 4칙 연산파일 다운로드1
11575정성태7/2/201819962Math: 28. GeoGebra 기하 (5) - 선분을 n 등분하는 방법파일 다운로드1
11574정성태7/2/201818455Math: 27. GeoGebra 기하 (4) - 선분을 n 배 늘이는 방법파일 다운로드1
11573정성태7/2/201817794Math: 26. GeoGebra 기하 (3) - 평행선
11572정성태7/1/201817128.NET Framework: 783. C# 컴파일러가 허용하지 않는 (유효한) 코드를 컴파일해 테스트하는 방법
11571정성태7/1/201818577.NET Framework: 782. C# - JIRA에 등록된 Project의 Version 항목 추가하는 방법파일 다운로드1
11570정성태7/1/201818747Math: 25. GeoGebra 기하 (2) - 임의의 선분과 특정 점을 지나는 수직선파일 다운로드1
11569정성태7/1/201817964Math: 24. GeoGebra 기하 (1) - 수직 이등분선파일 다운로드1
11568정성태7/1/201830183Math: 23. GeoGebra 기하 - 컴퍼스와 자를 이용한 작도 프로그램 [1]
11567정성태6/28/201819476.NET Framework: 781. C# - OpenCvSharp 사용 시 포인터를 이용한 속도 향상파일 다운로드1
11566정성태6/28/201825161.NET Framework: 780. C# - JIRA REST API 사용 정리 (1) Basic 인증 [4]파일 다운로드1
11565정성태6/28/201822027.NET Framework: 779. C# 7.3에서 enum을 boxing 없이 int로 변환하기 - 세 번째 이야기파일 다운로드1
11564정성태6/27/201820468.NET Framework: 778. (Unity가 사용하는) 모노 런타임의 __makeref 오류
11563정성태6/27/201819286개발 환경 구성: 386. .NET Framework Native compiler 프리뷰 버전 사용법 [2]
11562정성태6/26/201818768개발 환경 구성: 385. 레지스트리에 등록된 원격지 스크립트 COM 객체 실행 방법
11561정성태6/26/201830108.NET Framework: 777. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! [8]파일 다운로드1
11560정성태6/25/201821403.NET Framework: 776. C# 7.3 - 초기화 식에서 변수 사용 가능(expression variables in initializers)파일 다운로드1
11559정성태6/25/201828581개발 환경 구성: 384. 영문 설정의 Windows 10 명령행 창(cmd.exe)의 한글 지원 [6]
... 91  92  93  [94]  95  96  97  98  99  100  101  102  103  104  105  ...