IIS回收时如何确保DotNet Core的IHostedService执行完成
解决方案
核心思路是两层配合:一是正确实现托管服务的优雅停止逻辑,在收到关闭信号时等待正在运行的任务执行完成;二是调整IIS和托管模块的超时配置,给任务执行留出足够时间,避免进程被提前强杀。
1. 正确实现IHostedService的停止逻辑
不要直接在BackgroundService.ExecuteAsync中用不跟踪的Task.Run启动任务,必须保存正在运行的任务引用,在StopAsync方法中显式等待任务完成,同时配合取消令牌做超时兜底。
参考实现代码:
public class ScheduledJobHostedService : BackgroundService { private readonly ILogger<ScheduledJobHostedService> _logger; // 持有当前运行中任务的引用 private Task _inProgressTask = Task.CompletedTask; private CancellationTokenSource _jobCts = new(); public ScheduledJobHostedService(ILogger<ScheduledJobHostedService> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken hostStoppingToken) { // 宿主停止时触发任务取消通知 using var reg = hostStoppingToken.Register(() => _jobCts.Cancel()); // 按配置的间隔启动定时调度 var timer = new PeriodicTimer(TimeSpan.FromMinutes(5)); try { while (await timer.WaitForNextTickAsync(hostStoppingToken)) { // 避免任务重入:上一轮没跑完就跳过本次调度 if (!_inProgressTask.IsCompleted) { _logger.LogWarning("上轮计划任务未执行完成,跳过本次调度"); continue; } // 重置令牌源 _jobCts.Dispose(); _jobCts = new CancellationTokenSource(); // 启动任务,暂不await,保存引用供停止时等待 _inProgressTask = RunAllJobs(_jobCts.Token); } } catch (OperationCanceledException) { // 宿主正常停止,忽略取消异常 } } private async Task RunAllJobs(CancellationToken jobCancellationToken) { try { // 依次执行你的计划任务,必须传入取消令牌 // 长耗时步骤中要主动判断jobCancellationToken.IsCancellationRequested,及时做资源释放、事务回滚等收尾 await FirstJob(jobCancellationToken); await SecondJob(jobCancellationToken); } catch (Exception ex) { _logger.LogError(ex, "计划任务执行抛出异常"); } } public override async Task StopAsync(CancellationToken shutdownTimeoutToken) { // 先停止调度循环 await base.StopAsync(shutdownTimeoutToken); try { // 等待正在运行的任务完成,超时令牌触发后会自动抛出取消异常 await _inProgressTask.WaitAsync(shutdownTimeoutToken); _logger.LogInformation("运行中计划任务已全部执行完成,应用正常退出"); } catch (OperationCanceledException) { _logger.LogError("应用关闭超时,计划任务未执行完成即被终止,请检查任务是否存在死锁或过长耗时"); } finally { _jobCts.Dispose(); } } }
2. 调整IIS应用池关闭超时配置
IIS应用池默认给进程的优雅关闭时间只有90秒,如果你的计划任务最长耗时超过这个值,IIS会在超时后直接强制杀死w3wp进程,导致任务中断。
- 打开IIS管理器,找到站点对应的应用程序池,打开「高级设置」
- 找到
关闭时间限制(秒)选项,设置为大于单任务最长执行时长的值,比如单任务最多跑3分钟就设为240,留足冗余 - 建议同时把「启动32位应用程序」按你的部署需求设置正确,避免不必要的进程异常
3. 配置ASP.NET Core托管模块超时
IIS上托管.NET Core应用时,AspNetCore模块(ANCM)本身也有一层关闭超时限制,需要和应用池配置对齐:
找到站点发布目录下的web.config,修改<aspNetCore>节点,添加shutdownTimeLimit属性,值和应用池设置的秒数一致:
<configuration> <location path="." inheritInChildApplications="false"> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments="YourApp.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" shutdownTimeLimit="240" /> </system.webServer> </location> </configuration>
注意事项
- 所有计划任务的代码必须支持取消:长循环、IO操作等步骤要主动传入取消令牌,收到取消信号后不要继续执行业务逻辑,尽快完成收尾操作,避免超时被强杀。
- 禁止启动不受生命周期管理的后台任务:不要在托管服务中调用
Task.Run、Thread.Start启动游离的后台线程/任务,这类任务不会被StopAsync跟踪,关闭时必然被中断。 - 如果任务对可靠性要求极高(比如涉及资金、核心数据写入),不要依赖IIS托管的IHostedService运行,建议拆分到独立的Windows服务、后台作业调度系统中部署,避免IIS回收、站点异常等场景导致任务中断。
- 如果任务不允许重复执行,建议在任务启动时加分布式锁/本地文件锁,因为IIS默认是重叠回收模式,新进程启动后旧进程才会开始关闭,极端情况下可能出现新旧进程同时跑同一任务的情况。
内容的提问来源于stack exchange,提问作者rst
相关产品推荐
相关产品推荐

