How to exit gracefully from. NET Worker Service

Posted by Anidazen on Wed, 09 Feb 2022 17:23:48 +0100

We learned in the last article NET Worker Service [1]. Today we'll talk about how to gracefully close and exit Worker Service.

Worker class

Coupon website m.fenfaw.com net

From the previous article, we know that the Worker Service template provides us with three core files out of the box. The Worker class inherits from the abstract base class BackgroundService, which implements the IHostedService interface. Finally, the Worker class will be registered as a managed service, and the core code of our task processing is written in the Worker class. Therefore, we need to focus on workers and their base classes.

Let's take a look at its base class BackgroundService:

There are three rewritable methods in the base class BackgroundService, which enable us to bind to the application life cycle:

  • Abstract method ExecuteAsync: a method that serves as the main entry point of an application. If this method exits, the application closes. We must implement it in Worker.
  • Virtual method StartAsync: called when the application starts. If necessary, you can override this method, which can be used to set resources at one time when the service is started; Of course, you can ignore it.
  • Virtual method StopAsync: called when the application closes. If necessary, you can override this method to release resources and destroy objects when closing; Of course, you can ignore it.

By default, Worker overrides only the necessary abstract method ExecuteAsync.

Create a new Worker Service project

Let's create a new Worker Service and use task Delay to simulate some operations that must be completed before closing to see if you can simulate elegant closing by simply delaying in ExecuteAsync.

Development tools needed:

  • Visual Studio Code: https://code.visualstudio.com/
  • abreast of the times. NET SDK: https://dotnet.microsoft.com/download

After installing the above tools, run the following command in the terminal to create a Worker Service project:

dotnet new Worker -n "MyService"

After creating the Worker Service, open the application in Visual Studio Code, then build and run it to ensure that everything is normal:

dotnet build
dotnet run

Press CTRL+C to close the service, and the service will exit immediately. By default, the Worker Service is closed directly! In many scenarios (such as queues in memory), this is not the result we want. Sometimes we have to complete some necessary resource recycling or transaction processing before the service is shut down.

When we look at the code of the Worker class, we can see that it only overrides the abstract method ExecuteAsync in the base class BackgroundService:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        await Task.Delay(1000, stoppingToken);
    }
}

Let's try to modify this method and do some business processing before exiting:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        // await Task.Delay(1000, stoppingToken);
        await Task.Delay(1000);
    }

    _logger.LogInformation("Wait for exit {time}", DateTimeOffset.Now);

    Task.Delay(60_000).Wait(); //Simulate what needs to be done before exiting

    _logger.LogInformation("sign out {time}", DateTimeOffset.Now);
}

Then test it to see if it will wait 60 seconds before closing as we expected.

dotnet build
dotnet run

Press CTRL+C to shut down the service. We will find that after outputting "wait for exit", it does not shut down after outputting "exit" for 60 seconds, but quickly exits. This is just like the familiar console application. By default, it will close directly when we click the close button in the upper right corner or press CTRL+C.

Worker Service exit gracefully

So, how can we achieve elegant exit?

The method is actually very simple, that is, inject ihostapplicationlife into our service, and then manually call the StopApplication method of ihostapplicationlife to close the application when the application stops.

Modify the constructor of the Worker and inject ihostapplicationlife:

private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly ILogger<Worker> _logger;

public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger)
{
    _hostApplicationLifetime = hostApplicationLifetime;
    _logger = logger;
}

Then, in ExecuteAsync, after processing the business logic that must be completed before exiting, manually call the StopApplication method of ihostapplicationlife. The following is the rich ExecuteAsync Code:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    try
    {
        // The actual business logic is implemented here
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                await SomeMethodThatDoesTheWork(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Global exception occurred. Will resume in a moment.");
            }

            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
    }
    finally
    {
        _logger.LogWarning("Exiting application...");
        GetOffWork(stoppingToken); //Work to be done before closing
        _hostApplicationLifetime.StopApplication(); //Manually call StopApplication
    }
}

private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken)
{
    _logger.LogInformation("I love work and work hard ing......");
    await Task.CompletedTask;
}

/// <summary>
///Work to be done before closing
/// </summary>
private void GetOffWork(CancellationToken cancellationToken)
{
    _logger.LogInformation("Oh, shit, there's an emergency bug Need to be completed before work!!!");

    _logger.LogInformation("Ah, I love working overtime. I have to work for another 20 seconds, Wait 1 ");

    Task.Delay(TimeSpan.FromSeconds(20)).Wait();

    _logger.LogInformation("Ah, I love to work overtime. I have to work for another minute, Wait 2 ");

    Task.Delay(TimeSpan.FromMinutes(1)).Wait();

    _logger.LogInformation("Ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha!");
}

At this point, run dotnet run again, and then press CTRL+C to close the service. You will find the work to be completed before closing. The service will not exit until GetOffWork runs.

So far, we have realized the elegant exit of Worker Service.

StartAsync and StopAsync

To further understand the Worker Service, let's enrich our code and rewrite the StartAsync and StopAsync methods of the base class BackgroundService:

public class Worker : BackgroundService
{
    private bool _isStopping = false; //Are you stopping work
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
    private readonly ILogger<Worker> _logger;

    public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger)
    {
        _hostApplicationLifetime = hostApplicationLifetime;
        _logger = logger;
    }

    public override Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("After work, it's another energetic day, output from StartAsync");
        return base.StartAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            // The actual business logic is implemented here
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                    await SomeMethodThatDoesTheWork(stoppingToken);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Global exception occurred. Will resume in a moment.");
                }

                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
            }
        }
        finally
        {
            _logger.LogWarning("Exiting application...");
            GetOffWork(stoppingToken); //Work to be done before closing
            _hostApplicationLifetime.StopApplication(); //Manually call StopApplication
        }
    }

    private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken)
    {
        if (_isStopping)
            _logger.LogInformation("Pretend you're still working hard ing...... Actually, I went to wash the cup");
        else
            _logger.LogInformation("I love work and work hard ing......");

        await Task.CompletedTask;
    }

    /// <summary>
    ///Work to be done before closing
    /// </summary>
    private void GetOffWork(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Oh, shit, there's an emergency bug Need to be completed before work!!!");

        _logger.LogInformation("Ah, I love working overtime. I have to work for another 20 seconds, Wait 1 ");

        Task.Delay(TimeSpan.FromSeconds(20)).Wait();

        _logger.LogInformation("Ah, I love to work overtime. I have to work for another minute, Wait 2 ");

        Task.Delay(TimeSpan.FromMinutes(1)).Wait();

        _logger.LogInformation("Ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha!");
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Great. It's time to get off work, output from StopAsync at: {time}", DateTimeOffset.Now);

        _isStopping = true;

        _logger.LogInformation("Go wash the teacup first", DateTimeOffset.Now);
        Task.Delay(30_000).Wait();
        _logger.LogInformation("The tea cup is ready.", DateTimeOffset.Now);

        _logger.LogInformation("It's time to get off work ^_^", DateTimeOffset.Now);

        return base.StopAsync(cancellationToken);
    }
}

Run it again

dotnet build
dotnet run

Then press CTRL+C to close the service and see what the running result is?

We can observe that when the Worker Service is started and closed, the running order of the three rewritable methods in the base class BackgroundService is shown in the following figure:

summary

In this article, I introduce the relevant knowledge of how to exit Worker Service gracefully through an example.

The Worker Service is still essentially a console application that executes a job. However, it can not only run directly as a console application, but also be installed as a Windows service using the sc.exe utility. It can also be deployed to a linux machine to run as a background process. I will introduce more knowledge about Worker Service in the future.

You can download the source code in this article from GitHub [2].


Author: Technical Translator
Produced by: technical translation station

  1. https://mp.weixin.qq.com/s/ujGkb5oaXq3lqX_g_eQ3_g Introduction to. Net worker service ↩ ︎

  2. https://github.com/ITTranslate/WorkerServiceGracefullyShutdown Source download ↩ ︎