BackgroundService and IHostedService

Josen_Earth發表於2024-08-05

IHostedService:

適用於需要更高靈活性和控制的場景。
需要自定義啟動和停止邏輯。
適用於複雜的後臺任務管理。
BackgroundService:

適用於需要簡單實現後臺任務的場景。
提供了一個方便的抽象,減少樣板程式碼。
適用於大多數常見的後臺任務。

public class MyHostedService : IHostedService
{
    private readonly ILogger<MyHostedService> _logger;
    private Timer _timer;

    public MyHostedService(ILogger<MyHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("MyHostedService is starting.");
        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("MyHostedService is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("MyHostedService is stopping.");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

在 BackgroundService 中,StopAsync 是用來優雅地停止服務的。當應用程式停止時,StopAsync 會被呼叫,並且 CancellationToken 會被觸發,這將會請求 ExecuteAsync 停止執行。然而,StopAsync 並不會等待 ExecuteAsync 自行完成;相反,StopAsync 會立即開始執行。

為了確保 ExecuteAsync 完成後再執行 StopAsync 中的邏輯,可以在 ExecuteAsync 內部檢測到取消請求時進行清理,然後將 ExecuteAsync 任務與 StopAsync 協調起來。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        var host = Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<MyBackgroundService>();
            })
            .Build();

        await host.RunAsync();
    }
}

public class MyBackgroundService : BackgroundService
{
    private readonly ILogger<MyBackgroundService> _logger;
    private Task _executingTask;
    private readonly IHostApplicationLifetime _applicationLifetime;
    private CancellationTokenSource _stoppingCts = new CancellationTokenSource();

    public MyBackgroundService(ILogger<MyBackgroundService> logger, IHostApplicationLifetime applicationLifetime)
    {
        _logger = logger;
        _applicationLifetime = applicationLifetime;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("MyBackgroundService is starting.");

        stoppingToken.Register(() => _logger.LogInformation("MyBackgroundService is stopping."));

        _executingTask = Task.Run(async () =>
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("MyBackgroundService is running.");
                await Task.Delay(5000, stoppingToken);
            }

            _logger.LogInformation("MyBackgroundService is completing background work.");
            // 在這裡新增任何清理邏輯,例如關閉資源等

            _logger.LogInformation("MyBackgroundService has stopped.");
        }, stoppingToken);

        return _executingTask;
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("MyBackgroundService is executing StopAsync.");

        // 請求停止
        if (_executingTask == null)
        {
            return;
        }

        try
        {
            _stoppingCts.Cancel();
        }
        finally
        {
            // 等待任務完成
            await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
        }
        
        _logger.LogInformation("MyBackgroundService has completed StopAsync.");
    }
    
    public override void Dispose()
    {
        _stoppingCts.Cancel();
        base.Dispose();
    }

    public async Task StopServiceAsync()
    {
        _logger.LogInformation("Stopping the service internally...");
        _applicationLifetime.StopApplication();
    }
}

參考:
BackgroundService Graceful Shutdown - Complete work and write to DB
https://stackoverflow.com/questions/70036809/backgroundservice-graceful-shutdown-complete-work-and-write-to-db