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

.NET Framework服务器改长任务启动逻辑后进程无法退出原因排查

问题

我有一个.NET Framework服务器,其启动流程包含一批静态变量的初始化操作。在服务器生命周期结束时,我们会取消任务、释放各类可释放资源并完成整体清理。由于代码中某部分启动耗时过长,我将其改为懒加载实现+后台任务的方式,通过“触发”所有懒加载对象,确保启动N分钟后它们全部就绪。

public class MyClass
{
    private ConcurrentDictionary<string, Lazy<Thing>> dictionary = new ConcurrentDictionary<string, Lazy<Thing>>();
    private CancellationTokenSource cts = new CancellationTokenSource();
    private Task bootingTask;

    public void Boot()
    {
        // Add to the dictionary of all the lazy things
        // For example: dictionary.TryAdd(thingKey, new Lazy<Thing>(CreateThing));
        // Note: Replace CreateThing with your actual method to create a Thing object

        bootingTask = Task.Factory.StartNew(() =>
        {
            foreach(var item in dictionary.Values)
            {
                // (lazy initter)
                var _ = item.Value;
            }
        }, cts.Token, TaskCreationOptions.LongRunning)
        .ContinueWith(t =>
        {
            // Log how long the task took
            // For example: Console.WriteLine($"Task took: {t.Result} ms");
        });
    }

    public void Shutdown()
    {
        // Cancel from the Task Cancellation source
        cts.Cancel();

        // Checks if the bootingTask is not null and waits for it
        if(bootingTask != null)
        {
            bootingTask.Wait();
        }

        // Dispose of the task cancellation source and the task
        cts.Dispose();
        bootingTask.Dispose();
    
        dictionary.Values.foreach(disposeThing);
        // Other stuff
        // ... We do reach here
    } 
}

目前遇到的问题是:做出上述修改后,服务器进程无法正常结束,这会影响应用通过NSSM完成重启(重启要求所有进程必须先终止)。

已知信息:

  • ContinueWith逻辑已触发,控制台可看到任务耗时日志;
  • 启动任务未出现日志或异常,原以为调用.Wait()会捕获到异常;
  • 代码可执行到.Wait()逻辑并继续向下运行,不会永久阻塞;
  • 使用Task.Factory.StartNew是为避免将耗时数分钟的任务直接放入线程池,故添加了LongRunning选项,可能配置存在问题,也可拆分为dictionary.Count个任务;
  • 运行时可正常使用字典中所有对象,访问过程无异常抛出。

请问:可能是什么原因导致进程无法被终止?

可能的原因分析

  • Thing对象持有未释放的前台线程或资源:Lazy<Thing>初始化的Thing实例可能内部创建了前台线程(默认线程为前台线程,会阻止进程终止),或持有未释放的句柄(如文件、网络、数据库连接)。即使调用了disposeThing,若释放逻辑不完整,这些资源仍会阻碍进程退出。
  • LongRunning任务的线程未正确回收:使用TaskCreationOptions.LongRunning时,.NET会创建独立的前台线程而非线程池线程。即便任务已完成,若线程内部存在未处理的循环、等待逻辑,前台线程会保持进程存活。
  • ContinueWith的调度上下文问题:默认情况下ContinueWith会沿用原任务的调度上下文,若原任务是LongRunning线程,后续的延续任务可能仍关联前台线程资源,导致线程无法被回收。可尝试指定TaskContinuationOptions.ExecuteSynchronously或切换到后台线程调度。
  • 静态变量未彻底清理:原静态变量初始化改为懒加载后,部分静态对象可能仍持有全局引用,未在Shutdown阶段释放。静态变量生命周期覆盖整个AppDomain,若它们持有线程或资源,会阻止AppDomain卸载,进而阻碍进程终止。
  • disposeThing方法存在逻辑缺陷:虽然代码执行到了dictionary.Values.foreach(disposeThing),但如果该方法未正确调用Thing的Dispose,或Dispose逻辑不彻底(仅释放部分资源),会导致进程无法正常退出。
  • 未处理的后台任务或回调:Thing对象初始化时可能注册了全局事件回调、定时任务(如Timer),这些回调持有对象引用且未在Shutdown时取消/注销,导致对象无法被GC回收,最终阻止进程终止。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:19:55