var builder = AppHost.CreateBuilder(); builder.Configuration.AddJsonFile("appsettings.json"); builder.Logging.AddRelaxedJsonConsole(options => { options.TimestampFormat = "yyyy-MM-dd HH:mm:ss"; }); builder.AddHostedService<TimerService>(); var cts = new CancellationTokenSource(10_000); var app = builder.Build(); await app.RunAsync(cts.Token);TimerService 实现如下:
file sealed class TimerService : TimerBaseBackgroundService { protected override TimeSpan Period => TimeSpan.FromSeconds(1); protected override Task TimedTask(CancellationToken cancellationToken) { Console.WriteLine(DateTimeOffset.Now); return Task.CompletedTask; } }TimerService 继承于 TimerBaseBackgroundService 实现如下,它是一个 BackgroundService,可以认为就是 .NET 框架里的 BackgroundService
public abstract class TimerBaseBackgroundService : BackgroundService { protected abstract TimeSpan Period { get; } protected abstract Task TimedTask(CancellationToken cancellationToken); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var timer = new PeriodicTimer(Period); while (await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false)) { await TimedTask(stoppingToken).ConfigureAwait(false); } } }跑下上面的示例,输出结果如下:
file interface IWebServer { Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); } file sealed class HttpListenerWebServer : IWebServer { private readonly IServiceProvider _serviceProvider; private readonly HttpListener _listener = new(); public HttpListenerWebServer(IServiceProvider serviceProvider) { // 堆代码 duidaima.com _serviceProvider = serviceProvider; } public async Task StartAsync(CancellationToken cancellationToken) { _listener.Prefixes.Add("http://localhost:5100/"); _listener.Start(); var logger = _serviceProvider.GetRequiredService<ILogger<HttpListenerWebServer>>(); logger.LogInformation("WebServer started"); while (!cancellationToken.IsCancellationRequested) { var listenerContext = await _listener.GetContextAsync(); try { await listenerContext.Response.OutputStream.WriteAsync("Hello World"u8.ToArray(), cancellationToken); } catch (Exception) when (!cancellationToken.IsCancellationRequested) { throw; } finally { listenerContext.Response.Close(); } } _listener.Stop(); } public Task StopAsync(CancellationToken cancellationToken) { _listener.Stop(); return Task.CompletedTask; } }
出于示例目的,这里的 web server 做了简化始终返回一个 Hello World,并且端口号写死了 5100,感兴趣的小伙伴可以结合之前我们实现的 Minimal。
AspNetCore 的逻辑实现 web server 和 http request 的处理
有了 WebServer 我们在添加一个 BackgroundService 来启动我们的 WebServer,实现如下:file sealed class WebServerHostedService : BackgroundService { private readonly IWebServer _server; public WebServerHostedService(IWebServer server) { _server = server; } public override async Task StopAsync(CancellationToken cancellationToken) { await _server.StopAsync(cancellationToken); await base.StopAsync(cancellationToken); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await _server.StartAsync(stoppingToken); } }最后我们再和我们的 host 组合起来:
var builder = AppHost.CreateBuilder(); builder.Configuration.AddJsonFile("appsettings.json"); builder.Logging.AddRelaxedJsonConsole(options => { options.TimestampFormat = "yyyy-MM-dd HH:mm:ss"; }); builder.Services.AddSingleton<IWebServer, HttpListenerWebServer>(); builder.AddHostedService<WebServerHostedService>(); var cts = new CancellationTokenSource(10_000); var app = builder.Build(); await app.RunAsync(cts.Token);接着跑起来我们的 server,访问一下试一下:
public interface IAppHostBuilder { /// <summary> /// Gets the set of key/value configuration properties. /// </summary> ConfigurationManager Configuration { get; } /// <summary> /// Gets a collection of logging providers for the application to compose. This is useful for adding new logging providers. /// </summary> ILoggingBuilder Logging { get; } /// <summary> /// Gets a collection of services for the application to compose. This is useful for adding user provided or framework provided services. /// </summary> IServiceCollection Services { get; } } public sealed class AppHostBuilder : IAppHostBuilder { private bool _hostBuilt; private readonly ServiceCollection _serviceCollection; internal AppHostBuilder(AppHostBuilderSettings? settings) { settings ??= new(); _serviceCollection = new ServiceCollection(); Configuration = settings.Configuration ?? new ConfigurationManager(); Logging = new LoggingBuilder(Services); _serviceCollection.AddSingleton<IConfiguration>(Configuration); _serviceCollection.AddLogging(); } public ConfigurationManager Configuration { get; } public ILoggingBuilder Logging { get; } public IServiceCollection Services => _serviceCollection; public AppHost Build() { if (_hostBuilt) { throw new InvalidOperationException("The AppHost had been created"); } _hostBuilt = true; #if NET7_0_OR_GREATER _serviceCollection.MakeReadOnly(); #endif var services = Services.BuildServiceProvider(); return new AppHost(services, Configuration); } private sealed class LoggingBuilder : ILoggingBuilder { public LoggingBuilder(IServiceCollection services) { Services = services; } public IServiceCollection Services { get; } } }AppHost 的实现代码较多,仅贴出一部分代码,具体代码可以参考 Github。
public interface IAppHost { IConfiguration Configuration { get; } IServiceProvider Services { get; } Task RunAsync(CancellationToken cancellationToken = default); } public sealed class AppHost : IAppHost { private const string AppHostStartingMessage = "AppHost starting", AppHostStartedMessage = "AppHost started. Press Ctrl+C to shut down", AppHostStoppingMessage = "AppHost stopping", AppHostStoppedMessage = "AppHost stopped" ; private readonly ILogger _logger; private readonly AppHostOptions _appHostOptions; private readonly IHostedService[] _hostedServices; private readonly IHostedLifecycleService[] _hostedLifecycleServices; public AppHost(IServiceProvider services, IConfiguration configuration) { Services = services; Configuration = configuration; _logger = services.GetRequiredService<ILogger<AppHost>>(); _appHostOptions = services.GetRequiredService<IOptions<AppHostOptions>>().Value; _hostedServices = services.GetServices<IHostedService>().ToArray(); _hostedLifecycleServices = _hostedServices.Select(x => x as IHostedLifecycleService) .WhereNotNull().ToArray(); } public IConfiguration Configuration { get; } public ILogger Logger => _logger; public IServiceProvider Services { get; } public async Task RunAsync(CancellationToken cancellationToken = default) { Debug.WriteLine(AppHostStartingMessage); _logger.LogInformation(AppHostStartingMessage); using var hostStopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(InvokeHelper.GetExitToken(), cancellationToken); #if NET6_0_OR_GREATER var waitForStopTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); hostStopTokenSource.Token.Register(() => waitForStopTask.TrySetResult()); #else var waitForStopTask = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously); hostStopTokenSource.Token.Register(() => waitForStopTask.TrySetResult(null)); #endif var exceptions = new List<Exception>(); var startTimeoutCts = new CancellationTokenSource(_appHostOptions.StartupTimeout); var hostStartCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, InvokeHelper.GetExitToken(), startTimeoutCts.Token); var hostStartCancellationToken = hostStartCancellationTokenSource.Token; await ForeachService(_hostedLifecycleServices, hostStartCancellationToken, _appHostOptions.ServicesStartConcurrently, !_appHostOptions.ServicesStartConcurrently, exceptions, async (service, cancelToken) => { await service.StartingAsync(cancelToken); }).ConfigureAwait(false); LogAndRethrow(); await ForeachService(_hostedServices, hostStartCancellationToken, _appHostOptions.ServicesStartConcurrently, !_appHostOptions.ServicesStartConcurrently, exceptions, async (service, cancelToken) => { await service.StartAsync(cancelToken); }).ConfigureAwait(false); LogAndRethrow(); await ForeachService(_hostedLifecycleServices, hostStartCancellationToken, _appHostOptions.ServicesStartConcurrently, !_appHostOptions.ServicesStartConcurrently, exceptions, async (service, cancelToken) => { await service.StartedAsync(cancelToken); }).ConfigureAwait(false); LogAndRethrow(); startTimeoutCts.Dispose(); Debug.WriteLine(AppHostStartedMessage); _logger.LogInformation(AppHostStartedMessage); await waitForStopTask.Task.ConfigureAwait(false); Debug.WriteLine(AppHostStoppingMessage); _logger.LogInformation(AppHostStoppingMessage); // reverse to keep first startup last stop when not in concurrent Array.Reverse(_hostedServices); Array.Reverse(_hostedLifecycleServices); var stopTimeoutCts = new CancellationTokenSource(_appHostOptions.ShutdownTimeout); var hostStopCancellationToken = stopTimeoutCts.Token; await ForeachService(_hostedLifecycleServices, hostStopCancellationToken, _appHostOptions.ServicesStopConcurrently, false, exceptions, async (service, cancelToken) => { await service.StoppingAsync(cancelToken); }).ConfigureAwait(false); await ForeachService(_hostedServices, hostStopCancellationToken, _appHostOptions.ServicesStopConcurrently, false, exceptions, async (service, cancelToken) => { await service.StopAsync(cancelToken); }).ConfigureAwait(false); await ForeachService(_hostedLifecycleServices, hostStopCancellationToken, _appHostOptions.ServicesStopConcurrently, false, exceptions, async (service, cancelToken) => { await service.StoppedAsync(cancelToken); }).ConfigureAwait(false); Debug.WriteLine(AppHostStoppedMessage); _logger.LogInformation(AppHostStoppedMessage); } public static AppHostBuilder CreateBuilder(AppHostBuilderSettings? settings = null) { return new AppHostBuilder(settings); } }为了方便后台任务,重新定义了 IHostedService/BackgroundService 实现基本和 .NET 框架里的一致,为了避免扩展方法冲突,这里的扩展方法是直接基于 IAppHostBuilder 而非 IServiceCollection
public static IAppHostBuilder AddHostedService<TService>(this IAppHostBuilder appHostBuilder) where TService : class, IHostedService { Guard.NotNull(appHostBuilder); appHostBuilder.Services.TryAddEnumerable( ServiceDescriptor.Describe(typeof(IHostedService), typeof(TService), ServiceLifetime.Singleton) ); return appHostBuilder; }总结