배경 및 개요
현대 소프트웨어 시스템에서는 특정 시간마다 반복되어야 하는 유지보수 업무나 알림 전송 같은 로직이 빈번히 요구됩니다. 예를 들어, 일정한 주기로 데이터를 집계하여 보고서로 배포하거나, 주기적인 파일 다운로드 및 상태 통지를 수행해야 할 경우 수동 개입 없이 자동으로 처리할 메커니즘이 필수적입니다. 이러한 니즈를 충족시키기 위해 C# 생태계에서 널리 쓰이는 오픈소스 프레임워크인 Quartz.NET 을 활용해 스케줄링 시스템을 구축하는 방법을 살펴보겠습니다.
개발 환경 준비
가장 먼저 Visual Studio 또는 IDE 내의 NuGet Package Manager 를 열어 Quartz 패키지를 검색하고 해당 프로젝트에 의존성으로 추가해야 합니다. 이 과정을 통해 관련 네임스페이스와 클래스들을 사용할 수 있는 기반이 마련됩니다.
작업 로직 정의 (Job 구현)
Quartz 는 실행할 단위 작업을 IJob 인터페이스를 구현한 클래스로 정의합니다. 실제 비즈니스 로직은 Execute 메서드 내부에 작성하며, 여기서는 간단한 로그 출력 예시를 구현해 보겠습니다. 변수명과 클래스 명을 변경하여 구체적인 사용 시나리오 (예: 데이터 정리) 를 반영했습니다.
public class DataCleanUpJob : IJob
{
public async Task Execute(IJobExecutionContext executionContext)
{
try
{
string logMessage = $"[실행시간:{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 데이터 정류 작업 시작";
Console.WriteLine(logMessage);
// 실제 비동기 비즈니스 로직 수행 영역
await Task.Delay(1000);
}
catch (Exception ex)
{
Console.WriteLine($"오류 발생: {ex.Message}");
}
}
}
스케줄러 초기화 및 구성
정의된 잡(Job) 을 언제, 어떻게 실행할지 결정하려면 트리거 (Trigger) 와 스케줄러 (Scheduler) 가 필요합니다. 먼저 표준 팩토리를 이용해 스케줄러 인스턴스를 생성한 후 활성화 시킵니다. 이후 개별 잡 세부 정보와 실행 조건인 트리거를 결합하여 등록합니다.
1. 단순 반복 트리거 설정
고정된 시간 간격으로 동작해야 하는 경우에는 SimpleScheduleBuilder 를 사용합니다. 아래 코드에서는 작업을 1 초 마다 무한히 반복하도록 설정하는 예를 보여줍니다.
var jobConfig = JobBuilder.Create<DataCleanUpJob>()
.WithIdentity("DailyCleanup", "MaintenanceGroup")
.Build();
var intervalTrigger = TriggerBuilder.Create()
.WithIdentity("IntervalTrigger", "MaintenanceGroup")
.WithSimpleSchedule(x =>
{
x.WithIntervalInSeconds(1).RepeatForever();
})
.Build();
// 스케줄러 생성 및 태스크 등록
var factory = new StdSchedulerFactory();
IScheduler taskManager = await factory.GetScheduler();
await taskManager.Start();
await taskManager.ScheduleJob(jobConfig, intervalTrigger);
고급 시간 지정: 크론 (Cron) 표현식
간단한 반복 외에도 요일, 달, 시각 등 복잡한 규칙을 적용해야 한다면 크론 (Cron) 표현식을 사용하는 것이 효율적입니다. Quartz.NET 의 크론 식은 공백으로 구분된 6 개 또는 7 개의 필드로 이루어집니다. 각 필드는 특정한 범위와 허용되는 특수 문자를 가집니다.
| 순서 | 필드명 | 허용값 범위 | 특수문자 |
|---|---|---|---|
| 1 | 초 (Seconds) | 0 ~ 59 | , - * / |
| 2 | 분 (Minutes) | 0 ~ 59 | , - * / |
| 3 | 시간 (Hours) | 0 ~ 23 | , - * / |
| 4 | 날짜 (Day of Month) | 1 ~ 31 | , - * ? / L W |
| 5 | 월 (Month) | 1 ~ 12 | , - * / |
| 6 | 요일 (Day of Week) | SUN-SAT 또는 1-7 | , - * ? / L # |
| 7 | 연도 (Year) | (선택항목) | , - * / |
예를 들어, 매일 오전 10 시 15 분 정각에 작업을 실행하려는 경우 0 15 10 * * ?라는 식을 입력하면 됩니다. 연도가 필요한 상황이나 요일을 지정할 때는 마지막 필드를 활용하거나 생략 처리를 적절히 조합합니다.
크론 표현식 적용 예제
아래 코드는 매일 매달 15 일에 10 시 15 분에만 실행되도록 구성된 스케줄을 보여주는 실전 예시입니다. 기존 단순 반복과는 달리 WithCronSchedule 메서드를 통해 문자열 형태의 식을 주입합니다.
var monthlyJob = JobBuilder.Create<DataCleanUpJob>()
.WithIdentity("MonthlyTask", "ReportingGroup")
.Build();
var cronTrigger = TriggerBuilder.Create()
.WithIdentity("MonthlyTrigger", "ReportingGroup")
.WithCronSchedule("0 15 10 15 * ?") // 매월 15 일 오전 10:15
.Build();
using (var schedulerService = await factory.GetScheduler())
{
await schedulerService.Start();
await schedulerService.ScheduleJob(monthlyJob, cronTrigger);
Console.WriteLine("스케줄러 서비스가 대기 모드 진입.");
}