“ 매주 목요일마다 당신이 항상 하던대로 신발끈을 묶으면 신발이 폭발한다고 생각해보라.
컴퓨터를 사용할 때는 이런 일이 항상 일어나는데도 아무도 불평할 생각을 안 한다. ”- Jef Raskin
맥의 아버지 - 애플컴퓨터의 매킨토시 프로젝트를 주도
IHostedService, CancellationToken, 범위 지정 서비스 완벽 이해 및 실전 적용
백그라운드 작업의 핵심, BackgroundService A to Z
ASP.NET Core는 웹 애플리케이션 개발을 위한 강력한 프레임워크이지만, 웹 요청 처리 외에도 다양한 백그라운드 작업 요구사항을 충족해야 합니다. 주기적인 작업 스케줄링, 이벤트 기반 비동기 처리, 장시간 연산 작업 관리 등 백그라운드 작업은 애플리케이션의 기능 확장과 효율성 증진에 필수적입니다. ASP.NET Core는 이러한 요구를 위해 IHostedService 인터페이스와 BackgroundService 추상 클래스를 핵심 구성 요소로 제공합니다. 본 글에서는 BackgroundService의 개념을 심층적으로 이해하고, 실제 개발에서 활용할 수 있는 다양한 방법과 추가 정보를 상세히 제공하여 ASP.NET 백그라운드 작업 전문가로 발돋움할 수 있도록 안내합니다.
목차
BackgroundService
Startup.cs (ASP.NET Core 5이하) - ConfigureServices
Program.cs (ASP .NET Core 6이상) - AddHostedService<T>()
IHostedService와 BackgroundService: 호스팅 서비스의 핵심
1. IHostedService : ASP.NET Core 호스트 환경에서 서비스의 생명주기(시작, 중지)를 관리하는 인터페이스입니다. 애플리케이션 시작 시점에 서비스를 시작하고, 종료 시점에 안전하게 서비스를 중지시키는 역할을 담당합니다. StartAsync(CancellationToken cancellationToken)와 StopAsync(CancellationToken cancellationToken) 두 개의 메서드를 포함하며, ASP.NET Core 호스트에 의해 호출됩니다.
2. BackgroundService : IHostedService 인터페이스를 구현하는 추상 클래스로, 백그라운드 작업 구현을 간소화합니다. ExecuteAsync(CancellationToken stoppingToken) 추상 메서드를 제공하며, 이 메서드 내에 실제 백그라운드 작업 로직을 구현합니다. BackgroundService는 서비스 시작 및 중지 로직을 기본적으로 제공하므로, 개발자는 ExecuteAsync 구현에만 집중할 수 있습니다.
3. 호스팅 서비스 생명주기 : ASP.NET Core 애플리케이션이 시작되면, 등록된 IHostedService 구현체들의 StartAsync 메서드가 순차적으로 호출됩니다. 애플리케이션 종료 시에는 StopAsync 메서드가 호출됩니다. 이러한 생명주기 관리를 통해 백그라운드 서비스는 애플리케이션과 함께 시작하고 종료되는 안정적인 실행 환경을 보장받습니다.
CancellationToken, 작업 취소의 안전장치
1. 역할 : CancellationToken은 비동기 작업의 취소 요청을 안전하게 처리하기 위한 메커니즘입니다. BackgroundService의 ExecuteAsync 메서드는 CancellationToken을 파라미터로 받아서, 서비스 중지 신호가 발생했을 때 작업을 안전하게 종료할 수 있도록 합니다.
2. 사용법 : ExecuteAsync 메서드 내에서 stoppingToken.IsCancellationRequested 속성을 주기적으로 확인하여 취소 요청 여부를 확인합니다. 취소 요청이 감지되면, 작업 루프를 종료하고, 필요한 정리 작업을 수행합니다. Task.Delay(..., stoppingToken)과 같이 CancellationToken을 받는 비동기 메서드를 사용하여 작업 중 대기 상태에서도 취소 요청에 즉시 반응할 수 있도록 구현하는 것이 중요합니다.
3. 안전한 종료 : CancellationToken을 사용하면, 백그라운드 작업이 갑작스럽게 중단되는 것을 방지하고, 리소스 누수나 데이터 불일치와 같은 문제를 예방할 수 있습니다.
ExecuteAsync 구현 패턴 및 활용
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 주기적으로 실행할 작업
_logger.LogInformation("작업 실행");
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); // 30초간격
}
}
주기적 작업 (Timed Task) : 일정 시간 간격으로 반복되는 작업을 구현하는 가장 일반적인 패턴입니다. Task.Delay를 사용하여 작업 간격을 조정하고, while (!stoppingToken.IsCancellationRequested) 루프를 통해 작업을 반복합니다.
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_eventSubscriber.Subscribe(async eventData => {
// 이벤트 처리
_logger.LogInformation($"이벤트수신: {eventData}");
await ProcessEventAsync(eventData, stoppingToken);
});
await Task.Delay(Timeout.Infinite, stoppingToken); // 이벤트 대기
}
이벤트 기반 작업 (Event-Driven Task) : 특정 이벤트 발생 시 작업을 실행하는 패턴입니다. 메시지 큐, 이벤트 버스, 또는 외부 시스템의 알림 등을 구독하여 이벤트를 감지하고, 해당 이벤트에 대한 처리 로직을 ExecuteAsync 내에서 구현합니다.
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("작업 시작");
await LongRunningOperationAsync(stoppingToken); // 장시간 작업 실행
_logger.LogInformation("작업 완료");
}
장시간 작업 (Long-Running Task) : 파일 처리, 데이터 분석, 외부 API 호출 등 시간이 오래 걸리는 작업을 백그라운드에서 처리하는 패턴입니다. 작업 시작 시점을 기준으로 작업을 실행하고, 작업 완료 후 다음 작업을 대기하거나 서비스를 종료합니다.
범위 지정 서비스 (Scoped Service) 활용
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = Services.CreateScope())
{
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
await scopedService.DoSomethingAsync();
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
문제점 : BackgroundService는 기본적으로 싱글톤(Singleton)으로 등록되어 애플리케이션 생명주기 동안 단 하나의 인스턴스만 생성됩니다. 싱글톤 서비스는 범위 지정 서비스(Scoped Service, HTTP 요청 당 인스턴스 생성)에 직접 의존할 수 없습니다. 범위 지정 서비스는 HTTP 요청 컨텍스트 내에서 동작하도록 설계되었기 때문입니다.
해결책 : IServiceScopeFactory를 사용하여 명시적으로 범위를 생성합니다. IServiceScopeFactory.CreateScope() 메서드를 호출하면 새로운 서비스 범위가 생성되고, 이 범위 내에서 scope.ServiceProvider.GetRequiredService<T>()를 통해 범위 지정 서비스를 안전하게 사용할 수 있습니다. 각 작업 실행마다 새로운 범위를 생성하여 범위 지정 서비스의 격리성과 독립성을 보장해야 합니다.
오류 처리 및 로깅
오류 처리 : ExecuteAsync 메서드 내에서 발생하는 예외를 적절히 처리해야 합니다. try-catch 블록을 사용하여 예외를 포착하고, 로깅하거나 재시도 로직을 구현하여 서비스의 안정성을 확보합니다. 예외가 발생했을 때 서비스가 중단되지 않고, 지속적으로 작업을 수행할 수 있도록 설계하는 것이 중요합니다.
로깅 : ILogger<T> 인터페이스를 사용하여 백그라운드 작업의 실행 상태, 오류 정보, 성능 지표 등을 로깅합니다. 로깅은 문제 발생 시 디버깅, 서비스 모니터링, 성능 분석 등에 유용한 정보를 제공합니다. 로그 레벨을 적절히 설정하여 필요한 정보를 효율적으로 기록하고 관리해야 합니다.
구성 (Configuration) 및 의존성 주입 (Dependency Injection)
구성 : IConfiguration 인터페이스를 통해 애플리케이션 구성 설정을 BackgroundService 내에서 사용할 수 있습니다. 작업 주기, 외부 시스템 연결 정보, 로깅 설정 등 백그라운드 서비스 동작에 필요한 설정을 구성 파일 또는 환경 변수를 통해 관리하고, IConfiguration을 통해 접근하여 유연성을 높입니다.
의존성 주입 : BackgroundService의 생성자(Constructor)를 통해 필요한 서비스들을 의존성 주입받아 사용할 수 있습니다. 로거(ILogger<T>), 구성(IConfiguration), 데이터 저장소 접근 객체, 외부 API 클라이언트 등 백그라운드 작업에 필요한 의존성을 주입받아 코드의 재사용성, 테스트 용이성, 유지보수성을 향상합니다.
Worker Service 템플릿
.NET Worker Service: ASP.NET Core는 웹 애플리케이션뿐만 아니라, 콘솔 기반 백그라운드 애플리케이션 개발을 위한 "Worker Service" 템플릿을 제공합니다. Worker Service 템플릿은 BackgroundService 기반으로 백그라운드 작업을 특화하여 개발하는 데 유용합니다. 웹 기능 없이 백그라운드 작업만 수행하는 애플리케이션을 개발할 때 Worker Service 템플릿을 사용하면, 불필요한 웹 관련 구성 요소를 제거하고, 백그라운드 작업에 최적화된 환경을 구축할 수 있습니다.
성능 고려 사항
비동기 프로그래밍 : ExecuteAsync 메서드 내에서 모든 작업을 비동기적으로 구현하여 스레드 블로킹을 최소화하고, 애플리케이션의 전체적인 성능을 향상해야 합니다. async 및 await 키워드를 적극적으로 활용하고, I/O 바운드 작업은 비동기 API를 사용하여 효율적으로 처리합니다.
작업 분할 : CPU 부하가 높은 작업은 여러 개의 작은 작업으로 분할하여 병렬 처리하거나, 별도의 스레드 또는 프로세스에서 실행하여 메인 스레드의 응답성을 유지합니다. Task.Run, Parallel.ForEach 등의 API를 활용하여 병렬성을 높일 수 있습니다.
리소스 관리 : 백그라운드 작업이 사용하는 리소스(메모리, CPU, 네트워크 연결 등)를 효율적으로 관리해야 합니다. 불필요한 리소스 낭비를 줄이고, 리소스 누수를 방지하여 안정적인 서비스 운영을 보장합니다. 특히, 장시간 실행되는 작업의 경우 메모리 누수에 주의해야 합니다.
모니터링 및 로깅 : 백그라운드 작업의 상태를 주기적으로 모니터링하고, 상세한 로그를 기록하여 문제 발생 시 신속하게 대응할 수 있도록 합니다. APM (Application Performance Monitoring) 도구를 활용하여 성능 지표를 수집하고, 시각화하여 시스템 상태를 효과적으로 파악할 수 있습니다.
마무리
ASP.NET C# BackgroundService는 단순한 백그라운드 작업 실행을 넘어, 애플리케이션의 안정성, 확장성, 효율성을 높이는 데 핵심적인 역할을 수행합니다. IHostedService, CancellationToken, 범위 지정 서비스, 오류 처리, 로깅, 구성 관리 등 다양한 개념과 활용 방법을 숙지하고, 실제 개발에 적용함으로써 더욱 강력하고 안정적인 ASP.NET Core 애플리케이션을 구축할 수 있습니다. 본 가이드에서 제시된 정보와 Best Practices를 바탕으로 여러분의 백그라운드 서비스 개발 역량을 한 단계 더 발전시키시기 바랍니다.