重写 IHostedService 以在所有任务完成后停止

rewrite an IHostedService to stop after all tasks finished

我有一个应用程序,通常应该是一个简单的控制台应用程序,被编程为计划任务,不时由 windows 任务计划程序调用。

该程序应该在两个数据库上启动一些更新,一个数据库一个服务。说 ContosoDatabase 应该由 ContosoService 更新。

最终它被编写为 .NET Core 应用程序,使用 并且可能不是最佳选择IHostedServices 作为服务的基础,就像这样:

public class ContosoService : IHostedService {
    private readonly ILogger<ContosoService> _log;
    private readonly IContosoRepository _repository;
    
    private Task executingTask;

    public ContosoService(
        ILogger<ContosoService> log,
        IContosoRepository repository,
        string mode) {
        _log = log;
        _repository = repository;
    }

    public Task StartAsync(CancellationToken cancellationToken) {
        _log.LogInformation(">>> {serviceName} started <<<", nameof(ContosoService));
        executingTask = ExcecuteAsync(cancellationToken);

        // If the task is completed then return it, 
        // this should bubble cancellation and failure to the caller
        if (executingTask.IsCompleted)
            return executingTask;

        // Otherwise it's running
        // >> don't want it to run!
        // >> it should end after all task finished!
        return Task.CompletedTask;
    }

    private async Task<bool> ExcecuteAsync(CancellationToken cancellationToken) {
        var myUsers = _repository.GetMyUsers();

        if (myUsers == null || myUsers.Count() == 0) {
            _log.LogWarning("{serviceName} has any entry to process, will stop", this.GetType().Name);
            return false;
        }
        else {
            // on mets à jour la liste des employés Agresso obtenue
            await _repository.UpdateUsersAsync(myUsers);
        }

        _log.LogInformation(">>> {serviceName} finished its tasks <<<", nameof(ContosoService));
        return true;
    }

    public Task StopAsync(CancellationToken cancellationToken) {
        _log.LogInformation(">>> {serviceName} stopped <<<", nameof(ContosoService));
        return Task.CompletedTask;
    }
}

我这样从 main 调用它:

public static void Main(string[] args)
{
    try {
        CreateHostBuilder(args).Build().Run();
    }
    catch (Exception ex) {
        Log.Fatal(ex, ">>> the application could not start <<<");
    }
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host
    .CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) => {
        var config = hostContext.Configuration;
        
        if (args.Contains("Alonso")) {
            services
            .AddHostedService(provider =>
                new AlonsoService(
                    provider.GetService<ILogger<AlonsoService>>(),
                    provider.GetService<IAlonsoRepository>()));
        }

        // if there also Cedig in the list, they can be run in parallel
        if (args.Contains("Contoso")) {
            services
            .AddHostedService(provider =>
                new ContosoService(
                    provider.GetService<ILogger<ContosoService>>(),
                    provider.GetService<IContosoRepository>()));
        }
    });

现在的问题是,应用程序不会在所有更新完成后停止。

有没有办法快速重写应用程序以使其在第二个服务完成其任务后停止?

我试着把Environment.Exit(0);放在最后

public static void Main(string[] args) {
    try {
        CreateHostBuilder(filteredArgs.ToArray()).Build().Run();                
    }
    catch (Exception ex) {
        //Log....
    }

    Environment.Exit(0); // here
}

但这似乎没有帮助:在所有任务完成后,应用程序仍然 运行。

HostedServices 是后台服务。恰恰相反:它们可以对应用程序启动和停止事件作出反应,以便它们可以优雅地结束。它们并不意味着在完成时停止您的主应用程序,它们可能与应用程序一样存在。

我想说简单的任务和等待所有任务会更好地为您服务。或者当你的后台作业完成它的工作并在 main 中处理它们时发送一些事件。

无论您选择什么触发器,您都可以通过注入 IHostApplicationLifetime 并在其上调用 StopApplication() 方法来停止 .net 应用程序。在早期版本中,它只是 IApplicationLifetime。

看着IHost Interface documentation the method run() does not stop until the host is shutdown. seems that StopAsync() did not stop the service. so Environment.Exit(0); was never reached. maybe use CancellationToken强行结束宿主,或者注入Environment.Exit(0);在 ContosoService class 中,如果可能,即使不是最优的。

按照@Maxim 的建议,我通过注入 IHostApplicationLifetimelastService 布尔值发现了这个肮脏但可行的解决方法:

public ConsosoService(
    IHostApplicationLifetime hostApplicationLifetime,
    // ...
    bool lastService) 
{ ... }

public async Task StartAsync(CancellationToken cancellationToken)
{
    // do the job

    if (_lastService)
        _hostApplicationLifetime.StopApplication(); 
    // stops the application and cancel/stops other services as well
}

这是另一种不需要创建托管服务的方法

using var host = CreateHostBuilder(args).Build();
await host.StartAsync();
using var scope = host.Services.CreateScope();
var worker = scope.ServiceProvider.GetService<Worker>();
await worker!.Run();
await host.StopAsync();

IHostBuilder CreateHostBuilder(string[] args) =>
     Host.CreateDefaultBuilder(args)
         .ConfigureServices(services => ConfigureServices(services));

void ConfigureServices(IServiceCollection services)
{
    //main class which does the work
    services.AddScoped<Worker>();
    //do some DB operations
    services.AddScoped<DbCtxt>();
}

完整代码https://github.com/raghavan-mk/dotnet/tree/main/DIInConsole