롱 러닝(오랫동안 주기적으로 수행하는 작업) 동작 구현하기 (.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 로 무한 반복으로 계속 메시지로 반복 처리했던 적도 있었는데 이것과 동일한 방식이 맞는건지…
문득 고민이 생겨서 글 남겨보았습니다.
좋은 방법을 공유해주시면 감사합니다!