Movatterモバイル変換


[0]ホーム

URL:


Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

Download Microsoft EdgeMore info about Internet Explorer and Microsoft Edge
Table of contentsExit editor mode

Worker services in .NET

Feedback

In this article

There are numerous reasons for creating long-running services such as:

  • Processing CPU-intensive data.
  • Queuing work items in the background.
  • Performing a time-based operation on a schedule.

Background service processing usually doesn't involve a user interface (UI), but UIs can be built around them. In the early days with .NET Framework, Windows developers could create Windows Services for these purposes. Now with .NET, you can use theBackgroundService, which is an implementation ofIHostedService, or implement your own.

With .NET, you're no longer restricted to Windows. You can develop cross-platform background services. Hosted services are logging, configuration, and dependency injection (DI) ready. They're a part of the extensions suite of libraries, meaning they're fundamental to all .NET workloads that work with thegeneric host.

Important

Installing the .NET SDK also installs theMicrosoft.NET.Sdk.Worker and the worker template. In other words, after installing the .NET SDK, you could create a new worker by using thedotnet new worker command. If you're using Visual Studio, the template is hidden until the optional ASP.NET and web development workload is installed.

Terminology

Many terms are mistakenly used synonymously. This section defines some of these terms to make their intent in this article more apparent.

  • Background Service: TheBackgroundService type.
  • Hosted Service: Implementations ofIHostedService, or theIHostedService itself.
  • Long-running Service: Any service that runs continuously.
  • Windows Service: TheWindows Service infrastructure, originally .NET Framework-centric but now accessible via .NET.
  • Worker Service: TheWorker Service template.

Worker Service template

The Worker Service template is available in the .NET CLI and Visual Studio. For more information, see.NET CLI,dotnet new worker - template. The template consists of aProgram andWorker class.

using App.WorkerService;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Services.AddHostedService<Worker>();IHost host = builder.Build();host.Run();

The precedingProgram class:

Template defaults

The Worker template doesn't enable server garbage collection (GC) by default, as there are numerous factors that play a role in determining its necessity. All of the scenarios that require long-running services should consider performance implications of this default. To enable server GC, add theServerGarbageCollection node to the project file:

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

Tradeoffs and considerations

EnabledDisabled
Efficient memory management: Automatically reclaims unused memory to prevent memory leaks and optimize resource usage.Improved real-time performance: Avoids potential pauses or interruptions caused by garbage collection in latency-sensitive applications.
Long-term stability: Helps maintain stable performance in long-running services by managing memory over extended periods.Resource efficiency: May conserve CPU and memory resources in resource-constrained environments.
Reduced maintenance: Minimizes the need for manual memory management, simplifying maintenance.Manual memory control: Provides fine-grained control over memory for specialized applications.
Predictable behavior: Contributes to consistent and predictable application behavior.Suitable for Short-lived processes: Minimizes the overhead of garbage collection for short-lived or ephemeral processes.

For more information regarding performance considerations, seeServer GC. For more information on configuring server GC, seeServer GC configuration examples.

Worker class

As for theWorker, the template provides a simple implementation.

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);        }    }}

The precedingWorker class is a subclass ofBackgroundService, which implementsIHostedService. TheBackgroundService is anabstract class and requires the subclass to implementBackgroundService.ExecuteAsync(CancellationToken). In the template implementation, theExecuteAsync loops once per second, logging the current date and time until the process is signaled to cancel.

The project file

The Worker template relies on the following project fileSdk:

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

For more information, see.NET project SDKs.

NuGet package

An app based on the Worker template uses theMicrosoft.NET.Sdk.Worker SDK and has an explicit package reference to theMicrosoft.Extensions.Hosting package.

Containers and cloud adaptability

With most modern .NET workloads, containers are a viable option. When creating a long-running service from the Worker template in Visual Studio, you can opt in toDocker support. Doing so creates aDockerfile that containerizes your .NET app. ADockerfile is a set of instructions to build an image. For .NET apps, theDockerfile usually sits in the root of the directory next to a solution file.

# 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 baseWORKDIR /appFROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS buildWORKDIR /srcCOPY ["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/buildFROM build AS publishRUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publishFROM base AS finalWORKDIR /appCOPY --from=publish /app/publish .ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

The precedingDockerfile steps include:

  • Setting the base image frommcr.microsoft.com/dotnet/runtime:8.0 as the aliasbase.
  • Changing the working directory to/app.
  • Setting thebuild alias from themcr.microsoft.com/dotnet/sdk:8.0 image.
  • Changing the working directory to/src.
  • Copying the contents and publishing the .NET app:
  • Relayering the .NET SDK image frommcr.microsoft.com/dotnet/runtime:8.0 (thebase alias).
  • Copying the published build output from the/publish.
  • Defining the entry point, which delegates todotnet App.BackgroundService.dll.

Tip

The MCR inmcr.microsoft.com stands for "Microsoft Container Registry", and is Microsoft's syndicated container catalog from the official Docker hub. TheMicrosoft syndicates container catalog article contains additional details.

When you target Docker as a deployment strategy for your .NET Worker Service, there are a few considerations in the project file:

<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>

In the preceding project file, the<DockerDefaultTargetOS> element specifiesLinux as its target. To target Windows containers, useWindows instead. TheMicrosoft.VisualStudio.Azure.Containers.Tools.Targets NuGet package is automatically added as a package reference whenDocker support is selected from the template.

For more information on Docker with .NET, seeTutorial: Containerize a .NET app. For more information on deploying to Azure, seeTutorial: Deploy a Worker Service to Azure.

Important

If you want to leverageUser Secrets with the Worker template, you'd have to explicitly reference theMicrosoft.Extensions.Configuration.UserSecrets NuGet package.

Hosted Service extensibility

TheIHostedService interface defines two methods:

These two methods serve aslifecycle methods - they're called during host start and stop events respectively.

Note

When overriding eitherStartAsync orStopAsync methods, you must call andawait thebase class method to ensure the service starts and/or shuts down properly.

Important

The interface serves as a generic-type parameter constraint on theAddHostedService<THostedService>(IServiceCollection) extension method, meaning only implementations are permitted. You're free to use the providedBackgroundService with a subclass, or implement your own entirely.

Signal completion

In most common scenarios, you don't need to explicitly signal the completion of a hosted service. When the host starts the services, they're designed to run until the host is stopped. In some scenarios, however, you might need to signal the completion of the entire host application when the service completes. To signal the completion, consider the followingWorker class:

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();    }}

In the preceding code, theBackgroundService.ExecuteAsync(CancellationToken) method doesn't loop, and when it's complete it callsIHostApplicationLifetime.StopApplication().

Important

This will signal to the host that it should stop, and without this call toStopApplication the host will continue to run indefinitely. If you intend to run a short-lived hosted service (run once scenario), and you want to use the Worker template, you must callStopApplication to signal the host to stop.

For more information, see:

Alternative approach

For a short-lived app that needs dependency injection, logging, and configuration, use the.NET Generic Host instead of the Worker template. This lets you use these features without theWorker class. A simple example of a short-lived app using the generic host might define a project file like the following:

<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.10" />  </ItemGroup></Project>

It'sProgram class might look something like the following:

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}

The preceding code creates aJobRunner service, which is a custom class that contains the logic for the job to run. TheRunAsync method is called on theJobRunner, and if it completes successfully, the app returns0. If an unhandled exception occurs, it logs the error and returns1.

In this simple scenario, theJobRunner class could look like this:

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.");    }}

You'd obviously need to add real logic to theRunAsync method, but this example demonstrates how to use the generic host for a short-lived app without the need for aWorker class, and without the need for explicitly signaling the completion of the host.

See also

Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, seeour contributor guide.

Feedback

Was this page helpful?

YesNoNo

Need help with this topic?

Want to try using Ask Learn to clarify or guide you through this topic?

Suggest a fix?

  • Last updated on

In this article

Was this page helpful?

YesNo
NoNeed help with this topic?

Want to try using Ask Learn to clarify or guide you through this topic?

Suggest a fix?