.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 和 Web 开发工作负载之前,模板将隐藏。

术语

许多术语被误用为同义词。 在本部分,我们提供了其中一些术语的定义,以使这些术语的意图更为直观。

  • 后台服务:引用 BackgroundService 类型。
  • 托管服务:实现 IHostedService 或引用 IHostedService 本身。
  • 长时间运行的服务:持续运行的任何服务。
  • Windows 服务:Windows 服务基础结构,最初以 .NET Framework 为中心,但现在可通过 .NET 访问。
  • 辅助角色服务:引用辅助角色服务模板。

辅助角色服务模板

辅助角色服务模板可用于 .NET CLI 和 Visual Studio。 有关详细信息,请参阅 .NET CLI,dotnet new worker - 模板。 模板由 ProgramWorker 类组成。

using App.WorkerService;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
    .Build();

await host.RunAsync();

前面的 Program 类:

提示

默认情况下,辅助角色服务模板不启用服务器垃圾回收 (GC)。 所有需要长时间运行服务的场景都应考虑此默认设置对性能的影响。 若要启用服务器 GC,将 ServerGarbageCollection 节点添加到项目文件:

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

有关性能注意事项的详细信息,请参阅服务器 GC。 有关配置服务器 GC 的详细信息,请参阅服务器 GC 配置示例

可以使用顶级语句重写模板中的 Program.cs 文件:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using App.WorkerService;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<Worker>();
    })
    .Build();

await host.RunAsync();

这在功能上等效于原始模板。 有关 C# 9 功能的详细信息,请参阅 C# 9.0 中的新增功能

对于 Worker,模板提供了一个简单的实现。

namespace App.WorkerService
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}

前面的 Worker 类是 BackgroundService 的子类,用于实现 IHostedServiceBackgroundService 是一个 abstract 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 支持。 这将创建一个 Dockerfile,可用于容器化 .NET 应用。 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:6.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 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:6.0 中的基映像设置为别名 base
  • 将工作目录更改为 /app。
  • 设置 mcr.microsoft.com/dotnet/sdk:6.0 映像中的 build 别名。
  • 将工作目录更改为 /src。
  • 复制内容并发布 .NET 应用:
  • mcr.microsoft.com/dotnet/runtime:6.0base 别名)中继 .NET SDK 映像。
  • 从 /publish 复制已发布的生成输出。
  • 定义委托给 dotnet App.BackgroundService.dll 的入口点。

提示

mcr.microsoft.com 中的 MCR 代表“Microsoft Container Registry”,是 Microsoft 官方 Docker 中心的联合容器目录。 Microsoft 联合容器目录一文包含更多详细信息。

将 Docker 作为 .NET 辅助角色服务的部署策略时,项目文件中有几个注意事项:

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

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

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

在上一个项目文件中,<DockerDefaultTargetOS> 元素将 Linux 指定为其目标。 若要面向 Windows 容器,请改用 Windows。 从模板中选择 Docker 支持时,Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet 包会自动添加为包引用。

有关在 Docker 上使用 .NET 的详细信息,请参阅教程:容器化 .NET 应用。 有关部署到 Azure 的详细信息,请参阅教程:将辅助角色服务部署到 Azure

重要

如果要将用户机密与 Worker Service 模板结合使用,则必须明确引用 Microsoft.Extensions.Configuration.UserSecrets NuGet 包。

托管服务扩展性

IHostedService 接口定义两种方法:

这两种方法充当生命周期方法 - 它们分别在主机启动和停止事件期间被调用。

备注

如果重写 StartAsyncStopAsync 方法,必须调用(和 awaitbase 类方法,以确保服务正常启动和/或关闭。

重要

接口充当 AddHostedService<THostedService>(IServiceCollection) 扩展方法的泛型类型参数约束,这意味着只允许实现。 可以将提供的 BackgroundService 与子类一起使用,或完全实现自己的子类。

信号完成

在大多数常见场景中,无需显式指示托管服务的完成情况。 当主机启动服务时,它们被设计为运行,直到主机停止。 但是,在某些情况下,可能需要在服务完成后发出整个主机应用程序的完成信号。 若要实现此目的,请考虑以下 Worker 类:

namespace App.SignalCompletionService;

public class Worker : BackgroundService
{
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
    private readonly ILogger<Worker> _logger;

    public Worker(
        IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger) =>
        (_hostApplicationLifetime, _logger) = (hostApplicationLifetime, logger);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        _logger.LogInformation(
            "Worker running at: {time}", DateTimeOffset.Now);

        await Task.Delay(1000, stoppingToken);

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

在前面的代码中,ExecuteAsync 方法不循环,并在完成时调用 IHostApplicationLifetime.StopApplication()

重要

这会向主机发出信号,指示主机应停止,并且如果没有对 StopApplication 的调用,主机将继续无限期地运行。

有关详细信息,请参阅:

另请参阅