[ASP.NET Core 3框架揭秘]服务承载系统[3]:总体设计[上篇]

前面的实例演示了服务承载的基本编程模式,接下来我们从设计的角度来重新认识服务承载模型。总的来说,服务承载模型主要由如下图所示的三个核心对象组成:多个通过IHostedService接口表示的服务被承载于通过IHost接口表示的宿主上,IHostBuilder接口表示IHost对象的构建者。

一、IHostedService

承载的服务总是会被定义成IHostedService接口的实现类型。如下面的代码片段所示,该接口仅定义了两个用来启动和关闭自身服务的方法。当作为宿主的IHost对象被启动的时候,它会利用依赖注入框架激活每个注册的IHostedService服务,并通过调用StartAsync方法来启动它们。当服务承载应用程序关闭的时候,作为服务宿主的IHost对象会被关闭,由它承载的每个IHostedService服务对象的StopAsync方法也随之被调用。

public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);
    Task StopAsync(CancellationToken cancellationToken);
}

承载系统无缝集成了.NET Core的依赖注入框架,在服务承载过程中所需的依赖服务,包括承载服务自身和它所依赖的服务均由此框架提供,承载服务注册的本质就是将对应的IHostedService实现类型或者实例注册到依赖注入框架中。由于承载服务大都需要长时间运行直到应用被关闭,所以针对承载服务的注册一般采用Singleton生命周期模式。承载系统为承载服务的注册定义了如下这个AddHostedService<THostedService>扩展方法。由于该方法通过调用TryAddEnumerable扩展方法来注册服务,所以不用担心服务重复注册的问题。

public static class ServiceCollectionHostedServiceExtensions
{
    public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services) where THostedService: class, IHostedService
    {
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());
        return services;
    }
}

二、IHost

通过IHostedService接口表示的承载服务最终被承载于通过IHost接口表示的宿主上。一般来说,一个服务承载应用在整个生命周期内只会创建一个IHost对象,我们启动和关闭应用程序本质上就是启动和关闭作为宿主的IHost对象。如下面的代码片段所示,IHost接口派生于IDisposable接口,所以当它在关闭之后,应用程序还会调用其Dispose方法作一些额外的资源释放工作。IHost接口的Services属性返回作为依赖注入容器的IServiceProvider对象,该对象提供了服务承载过程中所需的服务实例,其中就包括需要承载的IHostedService服务。定义在IHost接口中的StartAsync和StopAsync方法完成了针对服务宿主的启动和关闭。

public interface IHost : IDisposable
{
    IServiceProvider Services { get; }
    Task StartAsync(CancellationToken cancellationToken = default);
    Task StopAsync(CancellationToken cancellationToken = default);
}

三、应用生命周期

在前面演示的实例中,在利用HostBuilder对象构建出IHost对象之后,我们并没有调用其StartAsync方法启动它,而是另一个名为Run的扩展方法。Run方法涉及到服务承载应用生命周期的管理,如果想了解该方法的本质,就得先来认识一个名为IHostApplicationLifetime的接口。顾名思义,IHostApplicationLifetime接口体现了服务承载应用程序的生命周期。如下面的代码片段所示,该接口除了提供了三个CancellationToken类型的属性来检测应用何时开启与关闭之外,还提供了一个StopApplication来关闭应用程序。

public interface IHostApplicationLifetime
{
    CancellationToken ApplicationStarted { get; }
    CancellationToken ApplicationStopping { get; }
    CancellationToken ApplicationStopped { get; }

    void StopApplication();
}

如下所示的ApplicationLifetime类型是对IHostApplicationLifetime接口的默认实现。我们可以看到它实现的三个属性返回的CancellationToken对象来源于三个对应的CancellationTokenSource对象,后者对应着三个不同的方法(NotifyStarted、StopApplication和NotifyStopped)。我们可以利用IHostApplicationLifetime服务的三个属性提供的CancellationToken对象得到关于应用被启动和关闭通知,这些通知最初就是由这三个对应的方法发出来的。

public class ApplicationLifetime : IHostApplicationLifetime
{
    private readonly ILogger<ApplicationLifetime> _logger;
    private readonly CancellationTokenSource _startedSource;
    private readonly CancellationTokenSource _stoppedSource;
    private readonly CancellationTokenSource _stoppingSource;

    public ApplicationLifetime(ILogger<ApplicationLifetime> logger)
    {
        _startedSource = new CancellationTokenSource();
        _stoppedSource = new CancellationTokenSource();
        _stoppingSource = new CancellationTokenSource();
        _logger = logger;
    }

    private void ExecuteHandlers(CancellationTokenSource cancel)
    {
        if (!cancel.IsCancellationRequested)
        {
            cancel.Cancel(false);
        }
    }

    public void NotifyStarted()
    {
        try
        {
            this.ExecuteHandlers(this._startedSource);
        }
        catch (Exception exception)
        {
            _logger.ApplicationError(6, "An error occurred starting the application",exception);
        }
    }

    public void NotifyStopped()
    {
        try
        {
            ExecuteHandlers(this._stoppedSource);
        }
        catch (Exception exception)
        {
            _logger.ApplicationError(8, "An error occurred stopping the application",exception);
        }
    }

    public void StopApplication()
    {
        CancellationTokenSource source = this._stoppingSource;
        lock (source)
        {
            try
            {
                ExecuteHandlers(this._stoppingSource);
            }
            catch (Exception exception)
            {
                _logger.ApplicationError(7, "An error occurred stopping the application", exception);
            }
        }
    }

    public CancellationToken ApplicationStarted => _startedSource.Token;
    public CancellationToken ApplicationStopped => _stoppedSource.Token;
    public CancellationToken ApplicationStopping => _stoppingSource.Token;
}

四、利用IHostApplicationLifetime关闭应用

我们接下来通过一个简单的实例来演示如何利用IHostApplicationLifetime服务来关闭整个承载应用程序。我们在一个控制台应用程序中定义了如下这个承载服务FakeHostedService。在FakeHostedService类型的构造函数中,我们注入了IHostApplicationLifetime服务。在得到其三个属性返回的CancellationToken对象之后,我们在它们上面分别注册了一个回调,回调操作通过在控制台上输出相应的文字使我们可以知道应用程序何时被启动和关闭。

public sealed class FakeHostedService : IHostedService
{
    private readonly IHostApplicationLifetime _lifetime;
    private IDisposable _tokenSource;

    public FakeHostedService(IHostApplicationLifetime lifetime)
    {
        _lifetime = lifetime;
        _lifetime.ApplicationStarted.Register(() => Console.WriteLine("[{0}]Application started", DateTimeOffset.Now));
        _lifetime.ApplicationStopping.Register(() => Console.WriteLine("[{0}]Application is stopping.", DateTimeOffset.Now));
        _lifetime.ApplicationStopped.Register(() => Console.WriteLine("[{0}]Application stopped.", DateTimeOffset.Now));
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token.Register(_lifetime.StopApplication);
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _tokenSource?.Dispose();
        return Task.CompletedTask;
    }
}

在实现的StartAsync方法中,我们采用如上的方式在5秒之后调用IHostApplicationLifetime服务的StopApplication方法来关闭整个应用程序。这个FakeHostedService服务最后采用如下的方式承载于当前应用程序中。

class Program
{
    static void Main()
    {
        new HostBuilder()
            .ConfigureServices(svcs => svcs.AddHostedService<FakeHostedService>())
            .Build()
            .Run();
    }
}

该程序运行之后会在控制台上输出如下图所示的结果,从三条消息产生的时间间隔我们可以确定当前应用程序正是承载FakeHostedService通过调用IHostApplicationLifetime服务的StopApplication方法关闭的。(源代码从这里下载)

五、Run扩展方法

如果我们调用IHost对象的扩展方法Run,它会在内部调用StartAsync方法,接下来它会持续等待下去直到接收到应用被关闭的通知。当IHost对象对象利用IHostApplicationLifetime服务接收到关于应用关闭的通知后,它会调用自身的StopAsync方法,针对Run方法的调用此时才会返回。启动IHost对象直到应用关闭这一实现体现在如下这个WaitForShutdownAsync扩展方法上。

public static class HostingAbstractionsHostExtensions
{
    public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
    {
        var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
        token.Register(state => ((IHostApplicationLifetime)state).StopApplication(), applicationLifetime);

        var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
        applicationLifetime.ApplicationStopping.Register(state =>
        {
            var tcs = (TaskCompletionSource<object>)state;
            tcs.TrySetResult(null);
        }, waitForStop);

        await waitForStop.Task;
        await host.StopAsync();
    }
}

如下所示的WaitForShutdown方法是上面这个WaitForShutdownAsync方法的同步版本。同步的Run方法和异步的RunAsync方法的实现也体现在下面的代码片段中。除此之外,下面的代码片段还提供了Start和StopAsync这两个扩展方法,前者可以视为StartAsync方法的同步版本,后者可以在关闭IHost对象的时候指定一个超时时限。

public static class HostingAbstractionsHostExtensions
{
    public static void WaitForShutdown(this IHost host) => host.WaitForShutdownAsync().GetAwaiter().GetResult();
    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);
            await host.WaitForShutdownAsync(token);
        }
        finally
        {
            host.Dispose();
        }
    }
    public static void Start(this IHost host) => host.StartAsync().GetAwaiter().GetResult();
    public static Task StopAsync(this IHost host, TimeSpan timeout) => host.StopAsync(new CancellationTokenSource(timeout).Token);
}

服务承载系统[1]: 承载长时间运行的服务[上篇]
服务承载系统[2]: 承载长时间运行的服务[下篇]
服务承载系统[3]: 总体设计[上篇]
服务承载系统[4]: 总体设计[下篇]
服务承载系统[5]: 承载服务启动流程[上篇]
服务承载系统[6]: 承载服务启动流程[下篇]

原文地址:https://www.cnblogs.com/artech/p/inside-asp-net-core-09-03.html

时间: 2024-10-10 20:36:36

[ASP.NET Core 3框架揭秘]服务承载系统[3]:总体设计[上篇]的相关文章

[ASP.NET Core 3框架揭秘]服务承载系统[5]: 承载服务启动流程[上篇]

我们在<总体设计[上篇]>和<总体设计[下篇]>中通过对IHostedService.IHost和IHostBuider这三个接口的介绍让读者朋友们对服务承载模型有了大致的了解.接下来我们从抽象转向具体,看看承载系统针对该模型的实现是如何落地的.要了解承载模型的默认实现,只需要了解IHost接口和IHostBuilder的默认实现类型就可以了.从下图所示的UML可以看出,这两个接口的默认实现类型分别是Host和HostBuilder,本篇将会着重介绍这两个类型. 一.服务宿主 Ho

[ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务

毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容器不仅为ASP.NET Core框架自身提供必要的服务,同时也是应用程序的服务提供者,依赖注入已经成为了ASP.NET Core应用的基本编程模式. 一.服务的注册与消费 为了让读者朋友们能够更加容易地认识.NET Core提供的依赖注入框架,我在"<一个迷你版DI框架>"中特

[ASP.NET Core 3框架揭秘] Options[4]: Options模型[下篇]

六.IOptionsMonitorCache<TOptions> IOptionsFactory<TOptions>解决了Options的创建与初始化问题,但由于它自身是无状态的,所以Options模型对Options对象实施缓存可以获得更好的性能.Options模型中针对Options对象的缓存由IOptionsMonitorCache<TOptions>对象来完成,如下所示的代码片段是该接口的定义. public interface IOptionsMonitorC

[ASP.NET Core 3框架揭秘] 依赖注入:控制反转

ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样会频繁地使用到它们.对于这里提到的这几个基础框架,依赖注入尤为重要.ASP.NET Core应用在启动以及后续针对请求的处理过程中,它会依赖各种的组件提供服务.为了便于定制,这些组件一般会以接口的形式进行"标准化",我们将这些标准化的组件统一称为"服务(Service)"

[ASP.NET Core 3框架揭秘] 依赖注入:依赖注入模式

原文:[ASP.NET Core 3框架揭秘] 依赖注入:依赖注入模式 IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式以不同的方式实现IoC,比如我们在前面介绍的模板方法.工厂方法和抽象工厂,接下来我们介绍一种更有价值的IoC模式:依赖注入(DI:Dependency Injection). 一.由容器提供对象 和前面介绍的工厂方法和抽象工厂模式一样,依

[ASP.NET Core 3框架揭秘] 依赖注入:一个Mini版的依赖注入框架

在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类似的原理创建了一个简易版本的依赖注入框架,也就是我们在前面多次提及的Cat. 源代码下载 普通服务的注册与消费泛型服务的注册与消费多服务实例的提供服务实例的生命周期 一.编程体验 虽然我们对这个名为Cat的依赖注入框架进行了最大限度的简化,但是与.NET Core框架内部使用的真实依赖注入框架相比,

[ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计

原文:[ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计 在<读取配置数据>([上篇],[下篇])上面一节中,我们通过实例的方式演示了几种典型的配置读取方式,接下来我们从设计的维度来重写认识配置模型.配置的编程模型涉及到三个核心对象,分别通过三个对应的接口(IConfiguration.IConfigurationSource和IConfigurationBuilder)来表示.如果从设计层面来审视背后的配置模型,还缺少另一个名通过IConfigurationProvide

[ASP.NET Core 3框架揭秘] 配置[6]:多样化的配置源[上篇]

.NET Core采用的这个全新的配置模型的一个主要的特点就是对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源.如果采用物理文件作为配置源,我们可以选择不同的格式(比如XML.JSON和INI等).如果这些默认支持的配置源形式还不能满足你的需求,我们还可以通过注册自定义IConfigurationSource的方式将其他形式数据作为配置来源. 一.MemoryConfigurationSource 在之前的实例演示都在使用MemoryConfigu

[ASP.NET Core 3框架揭秘] Options[7]: 与配置系统的整合

Options模型本身与配置系统完全没有关系,但是配置在大部分情况下会作为绑定Options对象的数据源,所以有必要将两者结合在一起.与<扩展与定制>演示的两个例子一样,针对配置系统的集成同样是通过定制Options模型相应的对象来实现的.具体来说,集成配置系统需要解决如下两个问题: 将承载配置数据的IConfiguration对象绑定为Options对象. 自动感知配置数据的变化. 第一个问题涉及针对Options对象的初始化问题,这自然是通过自定义IConfigureOptions<