闽公网安备 35020302035485号
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 框架里的 BackgroundServicepublic 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 而非 IServiceCollectionpublic 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;
}
总结