롱 러닝(오랫동안 주기적으로 수행하는 작업) 동작 구현하기 (.Net6 PeriodicTimer)

롱 러닝(오랫동안 주기적으로 수행하는 작업) 동작 구현하기 (.Net6 PeriodicTimer)

코딩 작업을 항상 하다 보면, 반복적으로 무한으로 수행해야 하는 메소드를 구현해야 할 때가 있고
그 반복작업의 생명 주기는 프로그램이 끝날때까지 계속돌려야 하는 것을 구현할 때가 있습니다.

예를 들면 하트비트나 데이터 실시간 동기 처리 등이 있을 수 있겠네요

제가 고민하면서 글을 올리는 이유가… 분명 저와 같은 고민을 하신 분이 계실 것인데, 이것을 어떻게 검색해서 고민을 말끔히 해결할 수 있을까? 무작정 고민하다가 닷넷 데브의 고수님들 의견을 들으면 좋겠다 싶어서 다음과 같이 질문 드립니다.

우선 기존에 무한 반복을 처리하기 위한 코드 예시는 다음과 같습니다.

                Task.Factory.StartNew(() =>
                {
                    ...
                    while (true)
                    {
                        
                        if (cancellationTokenSource.Token.IsCancellationRequested)
                            break;
                     // (내용)
                            Thread.Sleep(TimeSpan.FromMilliseconds(1));
                    }
                }, cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);

위와 같이 무한 반복 작업에 대해서는 무한 루프 내에서 처리하고자 하는 기능들을 넣고
마지막에 1밀리초의 대기시간(혹은 상황에 따라 적절한 대기)을 갖고 처리해왔었는데요,

커뮤니티 내에서 다음 글을 보고

이걸 사용하면 좀 더 깔끔하게 구현을 할 수 있을까? 해서 다음과 같이 구현해서 써보고 있습니다.

//인터페이스
public interface IPeriodicTimerWorker
{
    public void StartPeriodicTask(string name, TimeSpan timeSpan, Func<Task> todoTask, Serilogger? logger = null);
    public void StopPeriodicTask();
}
//구현
public class PeriodicTimerWorker : IPeriodicTimerWorker
{
    private string? timerName;
    private PeriodicTimer? periodicTimer;
    private CancellationTokenSource? cts;
    private Func<Task>? todoTask;
    private Task? periodicTask;
    private Serilogger? logger;

    public void StartPeriodicTask(string name, TimeSpan timeSpan, Func<Task> todoTask, Serilogger? logger = null)
    {
        timerName = name;
        periodicTimer = new PeriodicTimer(timeSpan);
        this.todoTask = todoTask;
        this.logger = logger;

        if (cts == null || cts?.IsCancellationRequested == true)
        {
            cts = new CancellationTokenSource();
            periodicTask = ActionPeriodicTask();
            logger?.Information($"{timerName} Start");
        }
    }

    public void StopPeriodicTask()
    {
        Task.Run(StopAndReleaseTask);
    }

    private async Task StopAndReleaseTask()
    {
        if (cts?.IsCancellationRequested == false)
        {
            cts?.Cancel();
            if (periodicTask != null)
                await periodicTask;
            cts?.Dispose();
            periodicTimer?.Dispose();
        }
    }
    private async Task ActionPeriodicTask()
    {
        try
        {
            while (periodicTimer != null && await periodicTimer.WaitForNextTickAsync(cts!.Token).ConfigureAwait(false))
            {
                if (todoTask != null)
                    await todoTask();
            }
        }
        catch (OperationCanceledException oce)
        {
            logger?.Information($"{timerName} {oce.Message}");
        }
        catch (Exception ex)
        {
            logger?.Fatal(ex.ToString());
        }
        logger?.Information($"{timerName} Finished");
    }
}

위처럼 만든 뒤 인터페이스 필드를 생성해서 반복할 작업에 태스크 메소드를 넣고 사용하고 있습니다.

알아서 잘 만들어서 쓰고 있는게 아니냐? 하며 질문할 수 있는데…
무한으로 감시하는 작업을 위 방식 말고 더 좋은 방식으로 구현하는 방식이 뭐가 좋을까? 하는 게 제 생각입니다.

옛날 MFC 할때는(?) win api PostMessage 로 무한 반복으로 계속 메시지로 반복 처리했던 적도 있었는데 이것과 동일한 방식이 맞는건지…

문득 고민이 생겨서 글 남겨보았습니다. :slight_smile:
좋은 방법을 공유해주시면 감사합니다!

2 Likes

원하시는 부분이 클라이언트쪽인지 서버쪽인지 잘 모르겠습니다.
서버쪽 관점에서 보자면 지원하는 대표적인 2가지 라이브러리들이 있습니다.

관련된 문서화나 예제들이 많으니 도입을 고려해 보시면 좋을 것 같습니다.

3 Likes

제가 바란게 blockingcollection 을 활용한
작업 produce consume 형태의 구현이었습니다.
오래되서 까먹고 잊다보니 이제 생각났네요 :flushed: