다음을 통해 공유


.NET의 작업자 서비스

다음과 같은 장기 실행 서비스를 만드는 데는 여러 가지 이유가 있습니다.

  • CPU 집약적 데이터 처리.
  • 백그라운드에서 작업 항목을 대기열에 추가합니다.
  • 일정에 따라 시간 기반 작업을 수행합니다.

백그라운드 서비스 처리에는 일반적으로 UI(사용자 인터페이스)가 포함되지 않지만 UI를 기반으로 빌드할 수 있습니다. .NET Framework를 사용하는 초기에 Windows 개발자는 이러한 용도로 Windows 서비스를 만들 수 있습니다. 이제 .NET을 사용하여 BackgroundService의 구현체인 IHostedService을 사용할 수 있으며, 직접 구현할 수도 있습니다.

.NET을 사용하면 더 이상 Windows로 제한되지 않습니다. 플랫폼 간 백그라운드 서비스를 개발할 수 있습니다. 호스팅된 서비스는 로깅, 구성 및 DI(종속성 주입)를 준비합니다. 라이브러리의 확장 제품군의 일부이므로 일반 호스트사용하는 모든 .NET 워크로드의 기본입니다.

중요하다

.NET SDK를 설치하면 Microsoft.NET.Sdk.Worker 및 작업자 템플릿도 설치됩니다. 즉, .NET SDK를 설치한 후 dotnet new worker 명령을 사용하여 새 작업자를 만들 수 있습니다. Visual Studio를 사용하는 경우 선택적 ASP.NET 및 웹 개발 워크로드가 설치될 때까지 템플릿이 숨겨집니다.

용어

많은 용어가 동의어로 잘못 사용됩니다. 이 섹션에서는 이 문서의 의도를 보다 명확하게 하기 위해 이러한 용어 중 일부를 정의합니다.

  • 백그라운드 서비스: BackgroundService 형식입니다.
  • 호스티드 서비스: IHostedService구현 또는 IHostedService 자체.
  • 장기 실행 서비스: 지속적으로 실행되는 모든 서비스.
  • Windows 서비스: Windows 서비스 인프라는 원래 .NET Framework 중심이지만 이제는 .NET을 통해 액세스할 수 있습니다.
  • 작업자 서비스: 작업자 서비스 템플릿입니다.

작업자 서비스 템플릿

작업자 서비스 템플릿은 .NET CLI 및 Visual Studio에서 사용할 수 있습니다. 자세한 내용은 .NET CLI, dotnet new worker - 템플릿참조하세요. 템플릿은 ProgramWorker 클래스로 구성됩니다.

using App.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

이전 Program 클래스:

  • HostApplicationBuilder만듭니다.
  • AddHostedService 호출하여 Worker 호스트된 서비스로 등록합니다.
  • 빌더에서 IHost을(를) 생성합니다.
  • 앱을 실행하는 Run 인스턴스에서 host 호출합니다.

템플릿 기본값

작업자 템플릿은 기본적으로 GC(서버 가비지 수집)를 사용하도록 설정하지 않습니다. 그 필요성을 결정하는 데 중요한 역할을 하는 여러 요소가 있기 때문에. 장기 실행 서비스가 필요한 모든 시나리오는 이 기본값의 성능 영향을 고려해야 합니다. 서버 GC를 사용하도록 설정하려면 프로젝트 파일에 ServerGarbageCollection 노드를 추가합니다.

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

절충 및 고려 사항

활성화됨 비활성화
효율적인 메모리 관리: 메모리 누수 방지 및 리소스 사용 최적화를 위해 사용되지 않는 메모리를 자동으로 회수합니다. 실시간 성능 향상: 대기 시간에 민감한 애플리케이션에서 가비지 수집으로 인한 잠재적 일시 중지 또는 중단을 방지합니다.
장기 안정성: 오랜 기간 동안 메모리를 관리하여 장기 실행 서비스에서 안정적인 성능을 유지하는 데 도움이 됩니다. 리소스 효율성: 리소스가 제한된 환경에서 CPU 및 메모리 리소스를 절약할 수 있습니다.
유지 관리 감소: 수동 메모리 관리의 필요성을 최소화하여 유지 관리를 간소화합니다. 수동 메모리 제어: 특수 애플리케이션에 대한 메모리에 대한 세분화된 제어를 제공합니다.
예측 가능한 동작: 일관되고 예측 가능한 애플리케이션 동작에 기여합니다. 단기 프로세스에 적합: 단기 또는 임시 프로세스에 대한 가비지 수집 오버헤드를 최소화합니다.

성능 고려 사항에 대한 자세한 내용은 Server GC참조하세요. 서버 GC를 구성하는 방법에 대한 자세한 내용은 Server GC 구성 예제를 참조하세요.

작업자 클래스

Worker템플릿은 간단한 구현을 제공합니다.

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

앞의 Worker 클래스는 BackgroundService구현하는 IHostedService하위 클래스입니다. BackgroundServiceabstract class이며 하위 클래스가 BackgroundService.ExecuteAsync(CancellationToken)를 구현해야 합니다. 템플릿 구현에서 ExecuteAsync 초당 한 번 반복되어 프로세스가 취소하라는 신호를 보낼 때까지 현재 날짜와 시간을 기록합니다.

프로젝트 파일

작업자 템플릿은 다음 프로젝트 파일 Sdk사용합니다.

<Project Sdk="Microsoft.NET.Sdk.Worker">

자세한 내용은 .NET 프로젝트 SDK 참조하세요.

NuGet 패키지

작업자 템플릿을 기반으로 하는 앱은 Microsoft.NET.Sdk.Worker SDK를 사용하며 Microsoft.Extensions.Hosting 패키지에 대한 명시적 패키지 참조가 있습니다.

컨테이너 및 클라우드 적응성

대부분의 최신 .NET 워크로드에서는 컨테이너가 실행 가능한 옵션입니다. Visual Studio의 작업자 템플릿에서 장기 실행 서비스를 만들 때 Docker 지원에 대해 옵트인할 수 있습니다. 이렇게 하면 .NET 앱을 컨테이너화하는 Dockerfile 만들어집니다. Dockerfile 이미지를 빌드하기 위한 지침 집합입니다. .NET 앱의 경우 Dockerfile 일반적으로 솔루션 파일 옆에 있는 디렉터리의 루트에 있습니다.

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:8.0@sha256:e6b552fd7a0302e4db30661b16537f7efcdc0b67790a47dbf67a5e798582d3a5 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /src
COPY ["background-service/App.WorkerService.csproj", "background-service/"]
RUN dotnet restore "background-service/App.WorkerService.csproj"
COPY . .
WORKDIR "/src/background-service"
RUN dotnet build "App.WorkerService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

이전 Dockerfile 단계는 다음과 같습니다.

  • 기본 이미지를 mcr.microsoft.com/dotnet/runtime:8.0에서 base이라는 별칭으로 설정합니다.
  • 작업 디렉터리를 /app 변경합니다.
  • build 이미지에서 mcr.microsoft.com/dotnet/sdk:8.0 별칭을 설정합니다.
  • 작업 디렉터리를 /src 변경합니다.
  • 콘텐츠를 복사하고 .NET 앱을 게시합니다.
  • mcr.microsoft.com/dotnet/runtime:8.0 .NET SDK 이미지를 재구성하고 있습니다 (base 별칭).
  • /publish에서 게시된 빌드 출력을 복사합니다.
  • dotnet App.BackgroundService.dll에 위임하는 진입점을 정의하기.

mcr.microsoft.com MCR은 "Microsoft Container Registry"를 의미하며 공식 Docker 허브에서 Microsoft의 신디케이트 컨테이너 카탈로그입니다. Microsoft 신디케이트 컨테이너 카탈로그 문서에는 추가 세부 정보가 포함되어 있습니다.

Docker를 .NET 작업자 서비스에 대한 배포 전략으로 대상으로 지정하는 경우 프로젝트 파일에 몇 가지 고려 사항이 있습니다.

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WorkerService</RootNamespace>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
  </ItemGroup>
</Project>

이전 프로젝트 파일에서 <DockerDefaultTargetOS> 요소는 Linux 대상으로 지정합니다. Windows 컨테이너를 대상으로 지정하려면 대신 Windows 사용합니다. Microsoft.VisualStudio.Azure.Containers.Tools.Targets 템플릿에서 선택하면 NuGet 패키지 패키지 참조로 자동으로 추가됩니다.

.NET을 사용하는 Docker에 대한 자세한 내용은 자습서: .NET 앱컨테이너화를 참조하세요. Azure에 배포하는 방법에 대한 자세한 내용은 자습서: Azure작업자 서비스 배포를 참조하세요.

중요하다

작업자 템플릿을 사용하여 사용자 비밀 활용하려면 Microsoft.Extensions.Configuration.UserSecrets NuGet 패키지를 명시적으로 참조해야 합니다.

호스트된 서비스 확장성

IHostedService 인터페이스는 두 가지 메서드를 정의합니다.

이러한 두 메서드는 수명 주기 메서드로 사용되며 호스트 시작 및 중지 이벤트 중에 각각 호출됩니다.

메모

StartAsync 또는 StopAsync 메서드를 재정의하는 경우 await 클래스 메서드를 호출하고 base 서비스가 제대로 시작 및/또는 종료되는지 확인해야 합니다.

중요하다

인터페이스는 AddHostedService<THostedService>(IServiceCollection) 확장 메서드에서 제네릭 형식 매개 변수 제약 조건으로 사용되므로 구현만 허용됩니다. 제공된 BackgroundService 서브클래스와 함께 사용하거나 완전히 구현할 수 있습니다.

신호 완성

대부분의 일반적인 시나리오에서는 호스트된 서비스의 완료를 명시적으로 알릴 필요가 없습니다. 호스트가 서비스를 시작하면 호스트가 중지될 때까지 실행되도록 설계되었습니다. 그러나 일부 시나리오에서는 서비스가 완료될 때 전체 호스트 애플리케이션의 완료를 신호로 표시해야 할 수 있습니다. 완료를 알리려면 다음 Worker 클래스를 고려합니다.

namespace App.SignalCompletionService;

public sealed class Worker(
    IHostApplicationLifetime hostApplicationLifetime,
    ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        logger.LogInformation(
            "Worker running at: {Time}", DateTimeOffset.Now);

        await Task.Delay(1_000, stoppingToken);

        // When completed, the entire app host will stop.
        hostApplicationLifetime.StopApplication();
    }
}

앞의 코드에서 BackgroundService.ExecuteAsync(CancellationToken) 메서드는 반복되지 않으며 완료되면 IHostApplicationLifetime.StopApplication()호출합니다.

중요하다

그러면 호스트가 중지되어야 한다는 신호가 표시되고, StopApplication 대한 이 호출이 없으면 호스트가 무기한으로 실행됩니다. 수명이 짧은 호스티드 서비스를 실행하려는 경우(시나리오를 한 번 실행) 작업자 템플릿을 사용하려는 경우 호스트에 중지 신호를 요청 StopApplication 해야 합니다.

자세한 내용은 다음을 참조하세요.

대체 방법

종속성 주입, 로깅 및 구성이 필요한 수명이 짧은 앱의 경우 작업자 템플릿 대신 .NET 제네릭 호스트 를 사용합니다. 이렇게 하면 클래스 없이 이러한 기능을 사용할 수 있습니다 Worker . 제네릭 호스트를 사용하는 단기 앱의 간단한 예제는 다음과 같이 프로젝트 파일을 정의할 수 있습니다.

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ShortLived.App</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
  </ItemGroup>
</Project>

클래스는 Program 다음과 같이 표시될 수 있습니다.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();

using var host = builder.Build();

try
{
    var runner = host.Services.GetRequiredService<JobRunner>();

    await runner.RunAsync();

    return 0; // success
}
catch (Exception ex)
{
    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    
    logger.LogError(ex, "Unhandled exception occurred during job execution.");

    return 1; // failure
}

앞의 코드는 작업을 실행할 논리를 포함하는 사용자 지정 클래스인 서비스를 만듭니다 JobRunner . 메서드가 RunAsync에서 JobRunner에 호출되고 성공적으로 완료되면 앱이 0를 반환합니다. 처리되지 않은 예외가 발생하면 오류를 기록하고 반환 1합니다.

이 간단한 시나리오에서 클래스는 JobRunner 다음과 같이 표시할 수 있습니다.

using Microsoft.Extensions.Logging;

internal sealed class JobRunner(ILogger<JobRunner> logger)
{
    public async Task RunAsync()
    {
        logger.LogInformation("Starting job...");

        // Simulate work
        await Task.Delay(1000);

        // Simulate failure
        // throw new InvalidOperationException("Something went wrong!");

        logger.LogInformation("Job completed successfully.");
    }
}

메서드에 RunAsync 실제 논리를 추가해야 하지만, 이 예제는 Worker 클래스가 없이도, 그리고 호스트 완료를 명시적으로 알리지 않고도 수명이 짧은 앱에 제네릭 호스트를 사용하는 방법을 보여 줍니다.

또한 참고하십시오