- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
导读
本文围绕 .NET runtime 仓库中Microsoft.Extensions.Hosting.Abstractions程序集(对应 NuGet 包Microsoft.Extensions.Hosting.Abstractions)的官方包说明文档展开,系统讲解 .NET 应用托管(Hosting)抽象层的核心设计:BackgroundService基类与IHostedService接口如何承载后台任务,IHostBuilder与IHost如何完成"配置—装配—启动—停止"的完整生命周期,以及应用环境信息与生命周期通知(Lifetime)的获取方式。读者学完后,将能独立基于这些抽象编写可长期运行、可优雅关停的 Worker 服务,并理解 ASP.NET Core 等应用模型为何建立在托管之上。
一、包定位:托管抽象的"最小公约数"
Microsoft.Extensions.Hosting.Abstractions是 .NET 托管体系的抽象层,其源码位于仓库 src/libraries/Microsoft.Extensions.Hosting.Abstractions/src。包说明文档(PACKAGE.md)给出的定位十分明确:它包含用于将用户代码"托管"在应用程序中的抽象,核心职责是封装应用的资源与生命周期功能,包括:
- 依赖注入(Dependency Injection):通过
IServiceCollection/IServiceProvider完成服务装配与解析; - 日志(Logging):通过
ILoggingBuilder等类型接入日志体系; - 配置(Configuration):通过
IConfiguration抽象统一管理各类配置源; - 启动、停止与通知获取(Starting, stopping and obtaining notifications):以宿主(Host)为单位编排应用的启动与关闭流程,并通过生命周期事件通知外部。
同时,该包也被用于"接线"建立在托管之上的特定应用模型,例如 ASP.NET Core——即文档中所说的"This package is also used to wire up specific application models like ASP.NET Core that are built on top of hosting."这意味着抽象层与具体应用模型解耦:任何应用(控制台、服务、Web)都可以共享同一套托管语义。
二、核心功能全景
根据 PACKAGE.md 的Key Features,该包提供三大类能力:
- 提供
BackgroundService基类与IHostedService接口,用于实现 Worker 服务(长期运行的后台任务); - 提供用于配置并启动/停止宿主的接口,即
IHostBuilder、IHost及其配套扩展方法; - 提供获取环境设置的类型,如应用名称(ApplicationName)、内容根路径(ContentRootPath)等环境信息。
下文将逐一展开,并深入到仓库源码确认其实现细节。
三、BackgroundService与IHostedService:Worker 服务的两大支柱
3.1 接口IHostedService:托管服务的最小契约
IHostedService定义了宿主管辖对象的最小契约,源码位于 src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostedService.cs:
public interface IHostedService { // 应用宿主准备好启动服务时触发;cancellationToken 表示启动过程被中止 Task StartAsync(CancellationToken cancellationToken); // 应用宿主执行优雅关闭时触发;cancellationToken 表示不再需要优雅关闭 Task StopAsync(CancellationToken cancellationToken); }它的关键语义是:宿主的StartAsync会依次启动所有注册的IHostedService,宿主的StopAsync会依次停止它们。任何希望跟随宿主生命周期运行的后台逻辑(消息队列消费、定时任务、TCP 监听等)都应实现该接口。
3.2 基类BackgroundService:一次实现ExecuteAsync即可
直接实现IHostedService需要自行管理StartAsync/StopAsync与取消令牌,绝大多数场景更推荐继承 BackgroundService.cs 中定义的抽象基类:
public abstract class BackgroundService : IHostedService, IDisposable { public virtual Task? ExecuteTask => _executeTask; protected abstract Task ExecuteAsync(CancellationToken stoppingToken); public virtual Task StartAsync(CancellationToken cancellationToken) { /* ... */ } public virtual async Task StopAsync(CancellationToken cancellationToken) { /* ... */ } public virtual void Dispose() { /* ... */ } }基类把IHostedService的样板代码全部接管,子类只需覆写ExecuteAsync。从源码可以清晰看到它的内部工作方式:
StartAsync:调用CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)创建链接触发令牌,将传入的取消令牌与内部停止令牌关联;随后通过Task.Run(() => ExecuteAsync(stoppingToken), CancellationToken.None)异步启动后台执行,并把任务存入_executeTask字段;方法始终返回Task.CompletedTask——注释明确指出"Any result from ExecuteAsync will be handled by the Host",即ExecuteAsync中的异常由宿主统一处理,避免启动流程被阻塞。StopAsync:若从未调用过StartAsync(_executeTask == null)则直接返回;否则调用_stoppingCts!.Cancel()向正在运行的ExecuteAsync发送停止信号,然后等待_executeTask完成。在 .NET 目标框架下使用_executeTask.WaitAsync(cancellationToken)实现"等它结束或超时中止",并指定ConfigureAwaitOptions.SuppressThrowing吞掉取消引发的异常;在非 .NET 框架下则退化为Task.WhenAny(_executeTask, tcs.Task)的实现。Dispose:取消_stoppingCts,确保资源被释放。
典型实现如下——一个每 5 秒输出一次日志的 Worker:
public sealed class TimedWorker : BackgroundService { private readonly ILogger<TimedWorker> _logger; public TimedWorker(ILogger<TimedWorker> logger) => _logger = logger; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } } }这里stoppingToken正是BackgroundService.StopAsync中_stoppingCts.Cancel()触发的那个令牌,优雅关闭时循环会在延迟结束后退出。
3.3 细粒度生命周期钩子:IHostedLifecycleService
如果需要在启动/停止前后插入额外逻辑,IHostedLifecycleService提供了四个钩子,源码见 src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostedLifecycleService.cs:
public interface IHostedLifecycleService : IHostedService { Task StartingAsync(CancellationToken cancellationToken); // StartAsync 之前 Task StartedAsync(CancellationToken cancellationToken); // StartAsync 之后 Task StoppingAsync(CancellationToken cancellationToken); // StopAsync 之前 Task StoppedAsync(CancellationToken cancellationToken); // StopAsync 之后 }它继承自IHostedService,宿主在遍历托管服务时会先检查服务是否实现了该接口,从而提供更细的生命周期编排能力。
四、宿主的配置、构建与启停:IHostBuilder与IHost
4.1IHostBuilder:程序初始化抽象
IHostBuilder.cs 定义了宿主构建抽象,全部方法均返回IHostBuilder自身以支持链式调用:
| 方法 | 职责 |
|---|---|
Properties | 宿主构建过程中组件间共享状态的中央存储(IDictionary<object, object>) |
ConfigureHostConfiguration(Action<IConfigurationBuilder>) | 配置"宿主自身"的配置,用于初始化IHostEnvironment;可多次调用,结果累加 |
ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder>) | 配置应用与后续构建流程的配置,结果可通过HostBuilderContext.Configuration与IHost.Services访问;可多次调用,结果累加 |
ConfigureServices(Action<HostBuilderContext, IServiceCollection>) | 向容器添加服务,用于构造IServiceProvider;可多次调用,结果累加 |
UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder>) | 替换默认的IServiceProvider工厂(如接入 Autofac 等第三方容器) |
UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>>) | 基于上下文的工厂重载;默认实现抛NotSupportedException,需要时由具体构建器覆写 |
ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder>) | 配置已实例化的依赖容器,可多次调用,结果累加 |
Build() | 执行初始化动作构建宿主,只能调用一次,返回IHost |
4.2IHost:程序抽象与运行扩展
构建完成后得到 IHost.cs:
public interface IHost : IDisposable { IServiceProvider Services { get; } // 程序配置的服务容器 Task StartAsync(CancellationToken cancellationToken = default); // 启动所有 IHostedService Task StopAsync(CancellationToken cancellationToken = default); // 优雅停止 }与之配套的 HostingAbstractionsHostExtensions.cs 提供了常用扩展:
Start():同步启动(StartAsync().GetAwaiter().GetResult());StopAsync(TimeSpan timeout):带超时的优雅停止,超时后允许终止剩余活动连接;WaitForShutdown()/WaitForShutdownAsync(token):阻塞直到 Ctrl+C 或 SIGTERM 触发关闭;内部从Services解析IHostApplicationLifetime,将外部令牌注册到StopApplication(),并等待ApplicationStopping信号后执行一次无取消令牌的StopAsync(避免误触发强制中断);Run()/RunAsync(token):StartAsync后WaitForShutdownAsync,无论正常退出还是异常,finally中都会释放宿主(优先走IAsyncDisposable,否则Dispose)。
一个最小控制台宿主示例:
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using IHost host = Host.CreateDefaultBuilder(args) // Host 在 Microsoft.Extensions.Hosting 包中 .ConfigureServices(services => services.AddHostedService<TimedWorker>()) .Build(); await host.RunAsync();五、环境设置:IHostEnvironment与常用常量
5.1IHostEnvironment
IHostEnvironment.cs 提供运行环境信息,共四个属性:
public interface IHostEnvironment { string EnvironmentName { get; set; } // 环境名,宿主自动从配置的 "environment" 键读取 string ApplicationName { get; set; } // 应用名,宿主自动设为包含入口点的程序集名 string ContentRootPath { get; set; } // 应用内容文件所在目录的绝对路径 IFileProvider ContentRootFileProvider { get; set; } // 指向 ContentRootPath 的文件提供器 }5.2 常用环境名:Environments
Environments.cs 定义了三个约定俗成的环境名常量:
Development("Development"):可启用生产环境不应暴露的特性;由于性能开销,作用域校验(scope validation)与依赖校验仅在开发环境执行;Staging("Staging"):用于上线前验证应用变更;Production("Production"):应配置为最大化安全、性能与健壮性。
5.3 配置键常量:HostDefaults
HostDefaults.cs 定义了宿主配置键:
ApplicationKey = "applicationName":设置IHostEnvironment.ApplicationName;EnvironmentKey = "environment":设置IHostEnvironment.EnvironmentName;ContentRootKey = "contentRoot":设置ContentRootPath与ContentRootFileProvider。
这些常量说明:环境名、应用名、内容根目录都可以通过宿主配置(环境变量、命令行、配置文件等)在启动阶段被覆盖。仓库中还提供 EnvironmentName.cs、HostEnvironmentEnvExtensions.cs 等辅助类型,例如IsDevelopment()、IsProduction()这类环境判断扩展方法,方便按环境差异化配置行为。
六、生命周期通知:IHostApplicationLifetime与IHostApplicationLifetime家族
IHostApplicationLifetime.cs 允许消费方订阅应用生命周期事件,接口注释明确说明它"不打算被用户替换"(not intended to be user-replaceable):
public interface IHostApplicationLifetime { CancellationToken ApplicationStarted { get; } // 宿主完全启动、即将等待优雅关闭时触发 CancellationToken ApplicationStopping { get; } // 开始优雅关闭时触发,仍有请求在途;阻塞直到所有回调完成 CancellationToken ApplicationStopped { get; } // 优雅关闭完成时触发,所有请求应已结束 void StopApplication(); // 请求终止当前应用 }典型用法:在ApplicationStopping上注册回调,执行关闭前的资源清理;或调用StopApplication()主动请求进程退出。此外还有 IApplicationLifetime.cs(旧的宿主生命周期接口)与 IHostLifetime.cs(负责监听外部关闭信号,如 Ctrl+C/SIGTERM)等配套抽象。
七、托管服务的注册:AddHostedService扩展
ServiceCollectionHostedServiceExtensions.cs 提供IServiceCollection上的注册扩展:
services.AddHostedService<TimedWorker>(); // 按类型注册 services.AddHostedService(sp => sp.GetRequiredService<SomeService>()); // 工厂方式注册源码实现细节值得注意:
- 内部通过
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>())注册——只注册IHostedService这一抽象映射,并不会把THostedService具体类型本身注册到容器,注释明确提示:"If you want to register the actual type, you must do so separately"(需要时可单独services.AddSingleton<SomeService>()); - 使用
TryAddEnumerable意味着同一类型多次调用不会被重复注册,保证宿主启动时每个服务只执行一次; - 注册的托管服务以单例(Singleton)生命周期存在,由宿主统一启停与释放;
- 泛型版本带有
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]特性,配合裁剪(Trimming)与 AOT 场景保留公开构造函数,便于依赖注入解析。
八、现代托管入口:IHostApplicationBuilder
较新的托管体系还引入了 IHostApplicationBuilder.cs,它把构建过程组织为可直接操作的对象集合,适合在Microsoft.Extensions.Hosting等上层包中由Host.CreateApplicationBuilder()创建:
Properties:构建期间共享状态;Configuration:IConfigurationManager,可变配置集,添加配置源会立即更新视图;Environment:IHostEnvironment环境信息;Logging:ILoggingBuilder,用于添加日志提供程序;Metrics:IMetricsBuilder,启用指标并定向输出;Services:IServiceCollection,组合用户与框架提供的服务;ConfigureContainer<TContainerBuilder>(factory, configure):注入自定义容器工厂;多次调用会替换先前存储的工厂与委托,IServiceProvider在构建时才创建,因此configure委托在所有服务注册完成后执行。
九、包间关系与依赖方向
PACKAGE.md 的Related Packages列出了与抽象层配套的包:Microsoft.Extensions.Hosting(通用宿主的实现包)、Microsoft.Extensions.Configuration.Abstractions、Microsoft.Extensions.DependencyInjection.Abstractions、Microsoft.Extensions.Diagnostics.Abstractions、Microsoft.Extensions.FileProviders.Abstractions、Microsoft.Extensions.Logging.Abstractions。从仓库结构看,这些依赖包均位于 src/libraries 下(如 Microsoft.Extensions.Configuration.Abstractions、Microsoft.Extensions.DependencyInjection.Abstractions、Microsoft.Extensions.Logging.Abstractions),它们为托管抽象提供配置、DI、日志等基础能力——抽象层只依赖这些"Abstractions"而非具体实现,正是其可被任意应用模型复用的关键。更上层的实现位于 Microsoft.Extensions.Hosting 包(如Host、HostBuilder、ConsoleLifetime等)。
十、实践建议与总结
- 优先继承
BackgroundService:只需实现ExecuteAsync(CancellationToken stoppingToken),循环体务必检查stoppingToken.IsCancellationRequested,并将令牌传给Task.Delay、ChannelReader.ReadAsync等可取消操作,才能实现优雅停止。 - 用
IHostedLifecycleService处理前后置逻辑:资源预热放在StartingAsync,收尾清理放在StoppingAsync/StoppedAsync。 - 借助
IHostApplicationLifetime做全局通知:在ApplicationStarted/ApplicationStopping上注册回调,比在单个服务内自管状态更可靠。 - 环境差异化配置:通过
HostDefaults.EnvironmentKey("environment")配合Environments常量与IHostEnvironment扩展方法,实现 Development/Staging/Production 三态隔离;注意开发环境的依赖与作用域校验特性。 - 宿主入口二选一:经典
IHostBuilder链式风格适合ConfigureAppConfiguration/ConfigureServices多次累加;现代IHostApplicationBuilder适合直接操作Configuration/Services/Logging对象。 - 理解注册语义:
AddHostedService只建立IHostedService映射,具体类型需单独注册;托管服务为单例,由宿主统一管理生命周期。
从抽象接口(IHostedService、IHostBuilder、IHost)到基类实现(BackgroundService),再到环境与生命周期通知(IHostEnvironment、IHostApplicationLifetime),Microsoft.Extensions.Hosting.Abstractions用最小的一组契约撑起了整个 .NET 托管生态。无论是写一个消息队列消费者,还是构建 ASP.NET Core 应用,理解这套抽象层都是深入 .NET 应用框架的必修课。
- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
相关推荐
Temporal Worker Commands 机制深度解析:基于 Nexus 的服务器到 Worker 命令通道
Temporal Worker Commands 机制深度解析:基于 Nexus 的服务器到 Worker 命令通道 Worker Commands 是 Tem
后端工作流自动化任务调度Linera Controller 应用深度解析:跨链 Worker 编排与分布式服务生命周期管理
Linera Controller 应用深度解析:跨链 Worker 编排与分布式服务生命周期管理 Controller 是 Linera 协议官方示例中用于
区块链Web3Celery Loader 机制深度解析:基于 celery.loaders.base 的配置加载、任务发现与生命周期钩子
Celery Loader 机制深度解析:基于 celery.loaders.base 的配置加载、任务发现与生命周期钩子 Celery 的 Loader(加载
任务调度后端消息队列
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考