How to exit gracefully from. NET Worker Service

Posted by Minor Threat on Fri, 11 Feb 2022 13:06:39 +0100

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:

Background Service

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 _logger;

public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger 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...);
await Task.CompletedTask;
}

///
///Work to be done before closing
///
private void GetOffWork(CancellationToken cancellationToken)
{
_ logger.LogInformation("Oh, shit, there's an urgent bug that needs 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 _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
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
Amazon evaluation www.yisuping.com cn
Shenzhen website construction www.sz886.com com