Written in front

This article further discusses some of the technical aspects of Host extensions in .NET Core 3.0 from the perspective of source code, focusing on the creation and protection of the Long Run Program.

About Host, the easiest thing we think of is the start and stop of the program, and the very key function hidden in it is the initialization of the Host. All the resources we need must be and should be initialized during the startup of the program. Of course, this article The main content is not the Host initialization, which has been described in the previous article. Of course, in order to better guard and manage the already started Host, .NET Core 3.0 exposes the subscription of the program’s lifecycle events to the developer, and of course includes the custom Host Service object.

Note: This code is based on .NET Core 3.0 Preview9


DotNET_Core_3.0_source__understanding_Host_(2)_0.png

 

Long Run Program in .NET Core 3.0

IHost and IHostBuilder

When we create the Long Run Program, we will first focus on the start and stop of the program. .NET Core 3.0 provides an interface IHost for this purpose. The interface is located in the Microsoft.Extensions.Hosting class library. The source code is as follows:

   1:  /// <summary>
   2:  /// A program abstraction.
   3:  /// </summary>
   4:  public interface IHost : IDisposable
   5:   {
   6:      /// <summary>
   7:      /// The programs configured services.
   8:      /// </summary>
   9:      IServiceProvider Services { get; }
  10:   
  11:      /// <summary>
  12:      /// Start the program.
  13:      /// </summary>
  14:      /// <param name="cancellationToken">Used to abort program start.</param>
  15:      /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> starts.</returns>
  16:      Task StartAsync(CancellationToken cancellationToken = default);
  17:   
  18:      /// <summary>
  19:      /// Attempts to gracefully stop the program.
  20:      /// </summary>
  21:      /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
  22:      /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> stops.</returns>
  23:      Task StopAsync(CancellationToken cancellationToken = default);
  24:   }

The interface contains a read-only property: IServiceProvider Services { get; }, which allows us to get all the object information injected during the initialization of the host.

The core function of the IHostBuilder interface is the initialization of the program. It is completed by IHost Build(), of course, it only needs to be run once. Its initialization content generally includes the following functions:


DotNET_Core_3.0_source__understanding_Host_(2)_1.png

 

In addition, it should be noted that the initialization of the above functions is to obtain the information input by the user through the interface provided by IHostBuilder, and then complete the initialization by calling the Build() method. The following is part of the source code of IHostBuilder:

   1:  /// <summary>
   2:  /// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostEnvironment"/>
   3:  /// for use later in the build process. This can be called multiple times and the results will be additive.
   4:  /// </summary>
   5:  /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
   6:  /// to construct the <see cref="IConfiguration"/> for the host.</param>
   7:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
   8:   public IHostBuilder ConfigureHostConfiguration (Action <IConfigurationBuilder> configureDelegate)
   9:   {
  10:      _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
  11:      return this;
  12:   }
  13:   
  14:  /// <summary>
  15:  /// Adds services to the container. This can be called multiple times and the results will be additive.
  16:  /// </summary>
  17:  /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
  18:  /// to construct the <see cref="IConfiguration"/> for the host.</param>
  19:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
  20:  public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
  21:   {
  22:      _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
  23:      return this;
  24:   }
  25:   
  26:  /// <summary>
  27:  /// Overrides the factory used to create the service provider.
  28:  /// </summary>
  29:  /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
  30:  /// <param name="factory">A factory used for creating service providers.</param>
  31:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
  32:  public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
  33:   {
  34:      _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
  35:      return this;
  36:   }
  37:   
  38:  /// <summary>
  39:  /// Enables configuring the instantiated dependency container. This can be called multiple times and
  40:  /// the results will be additive.
  41:  /// </summary>
  42:  /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
  43:  /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
  44:  /// to construct the <see cref="IConfiguration"/> for the host.</param>
  45:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
  46:  public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
  47:   {
  48:      _configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
  49:          ?? throw new ArgumentNullException(nameof(configureDelegate))));
  50:      return this;
  51:   }

IHostService

At the beginning of the article, I have said that the custom Host Service object, then how do we customize it, in fact, it is very simple to implement IHostService, and call services.AddHostedService<MyServiceA>() in ConfigureServices, the following is the source code of IHostService:

   1:  /// <summary>
   2:  /// Defines methods for objects that are managed by the host.
   3:  /// </summary>
   4:   public  interface HostedService
   5:   {
   6:      /// <summary>
   7:      /// Triggered when the application host is ready to start the service.
   8:      /// </summary>
   9:      /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
  10:      Task StartAsync(CancellationToken cancellationToken);
  11:   
  12:      /// <summary>
  13:      /// Triggered when the application host is performing a graceful shutdown.
  14:      /// </summary>
  15:      /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
  16:      Task StopAsync(CancellationToken cancellationToken);
  17:   }

According to the source code, we can know that there are only two methods for this interface, namely the method of starting and stopping the code program. The specific implementation can refer to the following:

   1:  public class MyServiceA : IHostedService, IDisposable
   2:   {
   3:      private bool _stopping;
   4:      private Task _backgroundTask;
   5:   
   6:      public MyServiceA(ILoggerFactory loggerFactory)
   7:       {
   8:          Logger = loggerFactory.CreateLogger<MyServiceB>();
   9:       }
  10:   
  11:      public ILogger Logger { get; }
  12:   
  13:      public Task StartAsync(CancellationToken cancellationToken)
  14:       {
  15:          Logger.LogInformation("MyServiceB is starting.");
  16:          _backgroundTask = BackgroundTask();
  17:          return Task.CompletedTask;
  18:       }
  19:   
  20:      private async Task BackgroundTask()
  21:       {
  22:          while (!_stopping)
  23:           {
  24:              await Task.Delay(TimeSpan.FromSeconds(7));
  25:              Logger.LogInformation("MyServiceB is doing background work.");
  26:           }
  27:   
  28:          Logger.LogInformation("MyServiceB background task is stopping.");
  29:       }
  30:   
  31:      public async Task StopAsync(CancellationToken cancellationToken)
  32:       {
  33:          Logger.LogInformation("MyServiceB is stopping.");
  34:          _stopping = true;
  35:          if (_backgroundTask != null)
  36:           {
  37:               // ALL: cancellation
  38:              await _backgroundTask;
  39:           }
  40:       }
  41:   
  42:       public  void Dispose ()
  43:       {
  44:          Logger.LogInformation("MyServiceB is disposing.");
  45:       }
  46:   }

IHostService is the entry point for our custom Host management object. All objects that need to be pushed into Host Hosting must implement this interface.

Host life cycle management

This interface provides a function that we can manage during the running of the program, such as the subscription of the start and stop events of the program. The management of the Host life cycle is mainly done by the interfaces of IHostApplicationLifetime and IHostLifetime.

The following is the source code of IHostApplicationLifetime

   1:   public  interface IHostApplicationLifetime
   2:   {
   3:      /// <summary>
   4:      /// Triggered when the application host has fully started.
   5:      /// </summary>
   6:      CancellationToken ApplicationStarted { get; }
   7:   
   8:      /// <summary>
   9:      /// Triggered when the application host is performing a graceful shutdown.
  10:      /// Shutdown will block until this event completes.
  11:      /// </summary>
  12:      CancellationToken ApplicationStopping { get; }
  13:   
  14:      /// <summary>
  15:      /// Triggered when the application host is performing a graceful shutdown.
  16:      /// Shutdown will block until this event completes.
  17:      /// </summary>
  18:      CancellationToken ApplicationStopped { get; }
  19:   
  20:      /// <summary>
  21:      /// Requests termination of the current application.
  22:      /// </summary>
  23:      void StopApplication();
  24:   }

The IHostLifetime source code is as follows:

   1:  public interface IHostLifetime
   2:   {
   3:      /// <summary>
   4:      /// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until it's complete before
   5:      /// continuing. This can be used to delay startup until signaled by an external event.
   6:      /// </summary>
   7:      /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
   8:      /// <returns>A <see cref="Task"/>.</returns>
   9:      Task WaitForStartAsync(CancellationToken cancellationToken);
  10:   
  11:      /// <summary>
  12:      /// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host is stopping and it's time to shut down.
  13:      /// </summary>
  14:      /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
  15:      /// <returns>A <see cref="Task"/>.</returns>
  16:      Task StopAsync(CancellationToken cancellationToken);
  17:   }

Specific use can refer to the following code:

   1:  public class MyLifetime : IHostLifetime, IDisposable
   2:   {
   3:       .........
   4:   
   5:      private IHostApplicationLifetime ApplicationLifetime { get; }
   6:   
   7:       public ConsoleLifetime (IHostApplicationLifetime applicationLifetime)
   8:       {
   9:          ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
  10:       }
  11:   
  12:      public Task WaitForStartAsync(CancellationToken cancellationToken)
  13:       {
  14:          _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
  15:           {
  16:              ((ConsoleLifetime)state).OnApplicationStarted();
  17:           },
  18:          this);
  19:          _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
  20:           {
  21:              ((ConsoleLifetime)state).OnApplicationStopping();
  22:           },
  23:          this);
  twenty four:   
  25:           .......
  26:   
  27:          return Task.CompletedTask;
  28:       }
  29:   
  30:      private void OnApplicationStarted()
  31:       {
  32:          Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
  33:          Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
  34:          Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
  35:       }
  36:   
  37:      private void OnApplicationStopping()
  38:       {
  39:          Logger.LogInformation("Application is shutting down...");
  40:       }
  41:   
  42:       ........
  43:   }

to sum up

At this point, we know a few points that need to be paid attention to when creating the Long Run Program, which is to inherit the IHostService, the life cycle time of the subscriber, and the initialization process of the Host. Relatively speaking, this content is relatively simple, but in the development process, there will still be many problems, such as the timing mechanism of the task, the access of the message, and the performance optimization of the program, etc., all of which need us in practice. Further summary and improvement.

Orignal link:https://www.cnblogs.com/edison0621/p/11520497.html