You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 5 Windows服务停止后进程仍运行问题求助

ASP.NET Core 5 Windows服务停止时进程残留、日志丢失问题解决办法

问题场景与现象

我通过IHostBuilder.UseWindowsService()将ASP.NET Core 5 API作为Windows服务运行,Main()方法代码如下:

try
{
    Log.Information("Starting Web Host");
    if (_isService)
    {
        builder.UseWindowsService();
    }

    var host = builder.Build();

    await host.RunAsync();
}
catch (Exception e)
{
    Log.Fatal(e, "Host terminated unexpectedly");
}
finally
{
    Log.Information("Shutting down, cancelling running actions...");
    cancellationTokenSource.Cancel();

    await Task.Delay(30000); // 测试用,实际需等待后台任务完成
    Log.Information("All tasks shut down, disposing logger.");
    Log.CloseAndFlush();
    cancellationTokenSource.Dispose();
}

停止服务时,预期服务会处于「正在停止」状态,直到finally块所有代码执行完毕才转为「已停止」,但实际服务几乎立即变为「已停止」,进程却持续运行至Task.Delay()结束,后续日志无法输出,重启服务会出现双进程,导致安装程序异常。


问题根源

Windows服务控制管理器(SCM)会在host.RunAsync()执行完成后立即标记服务为「已停止」,此时主机已释放大部分核心资源(包括日志系统的依赖),后续finally块的代码属于进程的残留逻辑,无法被SCM追踪,进而引发进程残留、日志丢失等问题。


解决步骤

1. 利用主机生命周期钩子处理停止逻辑

放弃在Main方法的finally块中处理后台任务等待,改用ASP.NET Core的IHostApplicationLifetime注册停止事件,让SCM正确等待停止流程完成:

try
{
    Log.Information("Starting Web Host");
    if (_isService)
    {
        builder.UseWindowsService();
    }

    var host = builder.Build();
    var appLifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
    var cancellationTokenSource = new CancellationTokenSource();

    // 注册服务停止触发的事件
    appLifetime.ApplicationStopping.Register(() =>
    {
        Log.Information("Shutting down, cancelling running actions...");
        cancellationTokenSource.Cancel();

        // 替换为实际的后台任务等待逻辑,此处用同步等待避免SCM提前结束
        Task.Delay(30000).Wait();
        
        Log.Information("All tasks shut down.");
    });

    await host.RunAsync();
}
catch (Exception e)
{
    Log.Fatal(e, "Host terminated unexpectedly");
}
finally
{
    Log.Information("Disposing logger.");
    Log.CloseAndFlush();
}

2. 用IHostedService封装后台任务(推荐)

如果存在长期运行的后台任务,将其封装为IHostedService实现,利用StopAsync方法处理停止等待,ASP.NET Core会自动在服务停止时调用该方法并等待执行完成:

public class BackgroundTaskService : IHostedService, IDisposable
{
    private Task _executingTask;
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _executingTask = ExecuteAsync(_cts.Token);
        return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        Log.Information("Stopping background task...");
        _cts.Cancel();

        // 等待任务完成,设置超时避免无限等待
        if (_executingTask != null)
        {
            await Task.WhenAny(_executingTask, Task.Delay(30000, cancellationToken));
        }
        Log.Information("Background task stopped.");
    }

    private async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 此处替换为实际后台任务逻辑
            await Task.Delay(1000, stoppingToken);
        }
    }

    public void Dispose()
    {
        _cts.Cancel();
        _cts.Dispose();
    }
}

在Program中注册该服务:

builder.Services.AddHostedService<BackgroundTaskService>();

3. 调整Windows服务停止超时

通过命令行调整服务的停止超时时间,确保SCM等待足够长的时间让停止逻辑完成:

sc config YourServiceName stoptimeout= 30000

(单位为毫秒,30000即30秒,需替换YourServiceName为实际服务名称)


效果验证

修改后,停止服务时SCM会等待停止逻辑执行完毕,服务会保持「正在停止」状态直到流程结束,进程会正常退出,日志可完整输出,不会出现双进程问题。

内容的提问来源于stack exchange,提问作者woutervanrobays

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 20:46:33