【.NET Core框架】服务承载系统(Hosting)
简介
.NET Core提供了承载(Hosting)系统,我们可以在它之上寄宿多个长时间运行的服务,ASP.NET Core应用仅仅是该承载系统的一种典型的服务类型而已,任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载。
Generic Host & WebHost
ASP.NET Core框架存在两个承载(Hosting)系统。
Web主机(WebHost)
在.NET Core 2.x时,ASP.NET Core 默认使用的是IWebHostBuilder/IWebHost为核心的承载系统,ASP.NET Core 3依然支持这样的应用承载方式;
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
通用主机(Generic Host)
而到了.NET Core 3.x,ASP.NET Core 默认选择使用Generic Host
Generic Host在.NET Core 2.1就已经存在了,并且它就是按照.NET Core未来版本的通用标准来实现的。不过由于当时的Generic Host只能用于非HTTP工作负载,所以.NET Core 2.x仍然使用的是 Web Host。不过到了.NET Core 3.x,Generic Host已经可以同时支持HTTP和非HTTP工作负载了。
在我们的ASP.NET Core应用中,需要创建一个Generic Host,并通过ConfigureWebHostDefaults等扩展方法针对Web Host进行配置。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
实际上,Web应用本身就是一个长时间运行的后台服务,我们完全可以定义一个承载服务,从而将Web应用承载于这个系统中。如下图所示,这个用来承载ASP.NET Core应用的承载服务类型为GenericWebHostService,这是一个实现了IHostedService接口的内部类型。
重要对象
- IHostedService接口表示承载服务。
- IHost接口表示承载服务的宿主。
- IHostBuilder接口表示IHost的建造者。
承载服务(IHostedService)
承载服务通过IHostedService接口表示,该接口定义的StartAsync方法和StopAsync方法可以启动与关闭服务。
一个ASP.NET Core应用本质上是一个需要长时间运行的服务,开启这个服务是为了启动一个网络监听器(Server)。当监听到抵达的HTTP请求之后,该监听器会将请求传递给应用提供的管道进行处理。管道完成了对请求处理之后会生成HTTP响应。
这个用来承载ASP.NET Core应用的承载服务类型为GenericWebHostService,这是一个实现了IHostedService接口的内部类型。
当作为宿主的IHost对象被启动的时候,它会利用依赖注入框架激活每个注册的IHostedService服务,并通过调用StartAsync方法来启动它们。当服务承载应用程序关闭的时候,作为服务宿主的IHost对象会被关闭,由它承载的每个IHostedService服务对象的StopAsync方法也随之被调用。
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
注册承载服务有以下两种方式,他们是等价的:
serviceCollection.AddSingleton<IHostedService, xxxxBackService>();
//由于该方法通过调用TryAddEnumerable扩展方法来注册服务,所以不用担心服务重复注册的问题
serviceCollection.AddHostedService<xxxxBackService>();
服务承载系统整合了依赖注入框架:
承载服务被注册到依赖注入框架中了。既然承载服务实例最终是通过依赖注入框架提供的,那么它自身所依赖的服务当然也可以注册到依赖注入框架中。
由于承载服务大都需要长时间运行直到应用被关闭,所以针对承载服务的注册一般采用Singleton生命周期模式。
承载服务的宿主(IHost)
承载服务最终被承载于通过IHost接口表示的宿主上。一般来说,一个服务承载应用在整个生命周期内只会创建一个IHost对象,我们启动和关闭应用程序本质上就是启动和关闭作为宿主的IHost对象。
IHost接口的Services属性返回作为依赖注入容器的IServiceProvider对象,该对象提供了服务承载过程中所需的服务实例,其中就包括需要承载的IHostedService服务。
public interface IHost : IDisposable
{
IServiceProvider Services { get; }
Task StartAsync(CancellationToken cancellationToken = default);
Task StopAsync(CancellationToken cancellationToken = default);
}
通用主机的启动流程
流程图:
源码
CreateDefaultBuilder方法
创建通用主机建造者
public static class Host
{
public static IHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new HostBuilder();
// 将 Content Root(项目根目录)设置为 Directory.GetCurrentDirectory (当前工作目录)
builder.UseContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureHostConfiguration(config =>
{
// 添加以 DOTNET_ 为前缀的环境变量(会将前缀删除作为环境变量的Key)
config.AddEnvironmentVariables(prefix: "DOTNET_");
if (args != null)
{
// 添加命令行参数 args
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostEnvironment env = hostingContext.HostingEnvironment;
// 默认当配置发生更改时,重载配置
bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
// appsettings.json、appsettings.{Environment}.json
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
// 启用 User Secrets(仅当运行在 Development 环境时)
if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
// 添加环境变量(未限定前缀)
// 目的是当应用(App)配置加载完毕后(注意是加载完毕后),允许读取所有环境变量,且优先级更高
// 即若存在多个同名的环境变量,不带前缀的比带前缀的优先级更高
config.AddEnvironmentVariables();
if (args != null)
{
// 添加命令行参数 args
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
// 添加 Logging 配置
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows)
{
// 在Windows平台上,添加 EventLogLoggerProvider
logging.AddEventLog();
}
logging.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId;
});
})
.UseDefaultServiceProvider((context, options) =>
{
// 启用范围验证 scope validation 和依赖关系验证 dependency validation(仅当运行在 Development 环境时)
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
}
ConfigureWebHostDefaults
public static class GenericHostBuilderExtensions
{
public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
return builder.ConfigureWebHost(webHostBuilder =>
{
WebHost.ConfigureWebDefaults(webHostBuilder);
// 执行 UseStartup 等
configure(webHostBuilder);
});
}
}
public static class GenericHostWebHostBuilderExtensions
{
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
return builder.ConfigureWebHost(configure, _ => { });
}
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder)
{
var webHostBuilderOptions = new WebHostBuilderOptions();
configureWebHostBuilder(webHostBuilderOptions);
// 重点1: GenericWebHostBuilder
var webhostBuilder = new GenericWebHostBuilder(builder, webHostBuilderOptions);
configure(webhostBuilder);
// 重点2:GenericWebHostService,将web主机服务(GenericWebHostService)附加到Host主机
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
return builder;
}
}
GenericWebHostBuilder
internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
{
_builder = builder;
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
if (!options.SuppressEnvironmentConfiguration)
{
// 添加以 ASPNETCORE_ 为前缀的环境变量(会将前缀删除作为环境变量的Key)
configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_");
}
_config = configBuilder.Build();
_builder.ConfigureHostConfiguration(config =>
{
// 添加到主机(Host)配置
config.AddConfiguration(_config);
// 执行 HostingStartups,详见下方的 ExecuteHostingStartups 方法
ExecuteHostingStartups();
});
_builder.ConfigureAppConfiguration((context, configurationBuilder) =>
{
// 在 ExecuteHostingStartups 方法中,该字段通常会被初始化
if (_hostingStartupWebHostBuilder != null)
{
var webhostContext = GetWebHostBuilderContext(context);
// 加载 HostingStartups 中添加的应用(App)配置
_hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
}
});
_builder.ConfigureServices((context, services) =>
{
var webhostContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
// 注册 IWebHostEnvironment
services.AddSingleton(webhostContext.HostingEnvironment);
services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.WebHostOptions = webHostOptions;
options.HostingStartupExceptions = _hostingStartupErrors;
});
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.TryAddSingleton<DiagnosticListener>(listener);
services.TryAddSingleton<DiagnosticSource>(listener);
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
// 注册 IHostingStartup 中配置的服务
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
UseStartup(startupType, context, services);
}
catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
{
var capture = ExceptionDispatchInfo.Capture(ex);
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = app =>
{
capture.Throw();
};
});
}
}
});
}
private void ExecuteHostingStartups()
{
var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
if (webHostOptions.PreventHostingStartup)
{
return;
}
var exceptions = new List<Exception>();
// 注意这里对 _hostingStartupWebHostBuilder 进行了初始化
_hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this);
// 从当前程序集和环境变量`ASPNETCORE_HOSTINGSTARTUPASSEMBLIES`配置的程序集列表(排除`ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES`中配置的程序集列表)中寻找特性`HostingStartupAttribute`,
// 并通过反射的方式创建特性所标识的`IHostingStartup`实现的实例,并调用其`Configure`方法。
foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
{
try
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
{
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
hostingStartup.Configure(_hostingStartupWebHostBuilder);
}
}
catch (Exception ex)
{
exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));
}
}
if (exceptions.Count > 0)
{
_hostingStartupErrors = new AggregateException(exceptions);
}
}
}
WebHost.ConfigureWebDefaults
public static class WebHost
{
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((ctx, cb) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
}
});
// 将 Kestrel 服务器设置为 Web 服务器,并添加配置
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
})
.ConfigureServices((hostingContext, services) =>
{
// 配置主机过滤中间件(Host Filtering)
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
// 当环境变量 ASPNETCORE_FORWARDEDHEADERS_ENABLED 为 true 时,添加转接头中间件(Forwarded Headers)
if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
}
services.AddRouting();
})
// 启用IIS集成
.UseIIS()
.UseIISIntegration();
}
}
我们通常会在ConfigureWebHostDefaults扩展方法的委托中调用UseStartup来指定Startup类,下面我们就来看一下UseStartup到底做了什么:将Startup.ConfigureServices中要注册的服务添加到ConfigureServices的委托中
public static class WebHostBuilderExtensions
{
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)]TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
{
return hostBuilder.UseStartup(typeof(TStartup));
}
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
{
// ...删除了一些代码
// 会进入该条件分支
// 不知道为什么进入该分支?上面让你牢记的 GenericWebHostBuilder 还记得吗?快去看看它实现了哪些接口
if (hostBuilder is ISupportsStartup supportsStartup)
{
return supportsStartup.UseStartup(startupType);
}
// ...删除了一些代码
}
}
internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
{
// 可以看到,虽然 UseStartup 可以调用多次,但是只有最后一次才有效
_startupObject = startupType;
// 将 Startup.ConfigureServices 中要注册的服务添加进来
// 好了,暂时看到这里就ok了
_builder.ConfigureServices((context, services) =>
{
if (object.ReferenceEquals(_startupObject, startupType))
{
UseStartup(startupType, context, services);
}
});
return this;
}
}
最后,看一下上面提到的第二个重点GenericWebHostService:用于后续Run方法时执行Configure(包括StartupFilters.Configure、Startup.Configure等)
GenericWebHostService
internal class GenericWebHostService : IHostedService
{
// 构造函数注入
public GenericWebHostServiceOptions Options { get; }
// 构造函数注入
public IEnumerable<IStartupFilter> StartupFilters { get; }
public async Task StartAsync(CancellationToken cancellationToken)
{
// ...删除了一些代码
RequestDelegate application = null;
try
{
// 这里取到了 Startup.Configure
// 可能你不知道为什么这里可以取到,别着急,文章后面会为你解释的
Action<IApplicationBuilder> configure = Options.ConfigureApplication;
// 要求 Startup 必须包含 Configure 方法,或必须调用 IWebHostBuilder.Configure
if (configure == null)
{
throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration.");
}
var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
// 注意:这里来执行 StartupFilters.Configure 与 Startup.Configure
// 将 Startup.Configure 与 StartupFilters.Configure 连接成中间件管道
// 为什么 Reverse?因为要先执行 StartupFilters.Configure,最后才执行 Startup.Configure,
// 所以用类似链条的方式,从尾巴开始向头部牵手,这样,最终得到的 configure 指向的就是头部
// 当执行 configure 时,就可以从头部流转到尾巴
foreach (var filter in StartupFilters.Reverse())
{
configure = filter.Configure(configure);
}
// 执行 Configure 方法
configure(builder);
// Build HTTP 请求管道
application = builder.Build();
}
catch (Exception ex)
{
Logger.ApplicationError(ex);
if (!Options.WebHostOptions.CaptureStartupErrors)
{
throw;
}
application = BuildErrorPageApplication(ex);
}
var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, HttpContextFactory);
await Server.StartAsync(httpApplication, cancellationToken);
// ...删除了一些代码
}
}
Build
public class HostBuilder : IHostBuilder
{
public IHost Build()
{
// 加载主机(Host)配置
BuildHostConfiguration();
// 实例化 HostingEnvironment
CreateHostingEnvironment();
// 实例化 HostBuilderContext
CreateHostBuilderContext();
// 加载应用(App)配置
BuildAppConfiguration();
// 注册服务并创建 Service Provider
CreateServiceProvider();
// 生成 IHost 实例并返回
return _appServices.GetRequiredService<IHost>();
}
}
BuildHostConfiguration
public class HostBuilder : IHostBuilder
{
private void BuildHostConfiguration()
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
// 加载主机(Host)配置(同时会执行上面所说的 IHostingStartup.Configure)
foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions)
{
buildAction(configBuilder);
}
_hostConfiguration = configBuilder.Build();
}
}
CreateHostingEnvironment
public class HostBuilder : IHostBuilder
{
private void CreateHostingEnvironment()
{
_hostingEnvironment = new HostingEnvironment()
{
ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],
EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
};
if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
{
_hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
}
_hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
}
}
CreateHostBuilderContext
public class HostBuilder : IHostBuilder
{
private void CreateHostBuilderContext()
{
_hostBuilderContext = new HostBuilderContext(Properties)
{
HostingEnvironment = _hostingEnvironment,
Configuration = _hostConfiguration
};
}
}
BuildAppConfiguration
public class HostBuilder : IHostBuilder
{
private void BuildAppConfiguration()
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
foreach (Action<HostBuilderContext, IConfigurationBuilder> buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
_hostBuilderContext.Configuration = _appConfiguration;
}
}
CreateServiceProvider
public class HostBuilder : IHostBuilder
{
private void CreateServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
// 注册 IHostEnvironment
services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
// 注册 HostBuilderContext
services.AddSingleton(_hostBuilderContext);
// 注册 IConfiguration,所以能在 Startup 中进行构造函数注入
services.AddSingleton(_ => _appConfiguration);
services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
// 注意这里注册了 IHostLifetime 服务的实例 ConsoleLifetime
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
// 注册 IHost 实例
services.AddSingleton<IHost, Internal.Host>();
services.AddOptions();
services.AddLogging();
// 执行 ConfigureServices 方法中的委托进行服务注册
// 包括使用扩展方法 ConfigureServices、 Startup.ConfigureServices 等设置的委托
foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
{
configureServicesAction(_hostBuilderContext, services);
}
object containerBuilder = _serviceProviderFactory.CreateBuilder(services);
// 加载容器配置
foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
{
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
}
// 创建 Service Provider
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
if (_appServices == null)
{
throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
}
_ = _appServices.GetService<IConfiguration>();
}
}
Run
public static class HostingAbstractionsHostExtensions
{
public static void Run(this IHost host)
{
host.RunAsync().GetAwaiter().GetResult();
}
public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
try
{
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
}
else
{
host.Dispose();
}
}
}
}
StartAsync
internal class Host : IHost, IAsyncDisposable
{
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token;
// _hostLifetime 是在构造函数注入的
// 还记得吗?在上面的 CreateServiceProvider 方法中,注入了该服务的默认实例 ConsoleLifetime,在下方你可以看到 ConsoleLifetime 的部分实现
await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false);
combinedCancellationToken.ThrowIfCancellationRequested();
// 这里面就包含我们上面提到的重点 GenericWebHostService
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (IHostedService hostedService in _hostedServices)
{
// 激活 IHostedService.StartAsync
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
}
// 激活 IHostApplicationLifetime.Started
_applicationLifetime.NotifyStarted();
_logger.Started();
}
}
public class ConsoleLifetime : IHostLifetime, IDisposable
{
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
// ...删除了一些代码
// 注册了程序退出回调
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
// 注册了 Ctrl + C 回调(这下你知道为啥执行了 Ctrl + C 程序就退出了吧?)
Console.CancelKeyPress += OnCancelKeyPress;
// 立即启动 Console applications
return Task.CompletedTask;
}
private void OnProcessExit(object sender, EventArgs e)
{
ApplicationLifetime.StopApplication();
if (!_shutdownBlock.WaitOne(HostOptions.ShutdownTimeout))
{
Logger.LogInformation("Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks.");
}
_shutdownBlock.WaitOne();
System.Environment.ExitCode = 0;
}
private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
e.Cancel = true;
ApplicationLifetime.StopApplication();
}
}
WaitForShutdownAsync
public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
{
IHostApplicationLifetime applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
token.Register(state =>
{
((IHostApplicationLifetime)state).StopApplication();
},
applicationLifetime);
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
applicationLifetime.ApplicationStopping.Register(obj =>
{
var tcs = (TaskCompletionSource<object>)obj;
tcs.TrySetResult(null);
}, waitForStop);
// 正是由于此处,程序 Run 起来后,在 applicationLifetime.ApplicationStopping 被触发前,能够一直保持运行状态
await waitForStop.Task.ConfigureAwait(false);
await host.StopAsync(CancellationToken.None).ConfigureAwait(false);
}
Host的整个启动流程,就差不多说完了。
参考:
https://www.cnblogs.com/artech/p/inside-asp-net-core-3.html
https://www.cnblogs.com/xiaoxiaotank/p/15273093.html