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

TPL Dataflow模拟身份运行管道时泄漏数百万句柄求助

模拟身份下TPL Dataflow/Parallel操作引发句柄泄漏与系统资源耗尽问题

我的WinForms应用在执行大型TPL Dataflow管道操作时频繁崩溃至桌面,常规try/catch无法捕获异常,仅在最外层Program.cs/Main()中添加try/catch后才捕获到:
Unhandled Exception: System.Security.SecurityException: Insufficient system resources exist to complete the requested service.
注:问题发生在配备384GB内存和56个逻辑处理器的机器上。

通过Sysinternals Process Explorer监控句柄计数发现:仅在模拟身份时,进程会占用多达1400万个句柄;未模拟身份时,同一操作仅占用350-400个句柄。

复现步骤

只要拥有可用于模拟身份的备用账号即可轻松复现:

  • 参考WindowsIdentity.Impersonate的官方示例代码实现模拟身份的控制台应用
  • 添加本文提供的空操作TPL Dataflow类
  • 在模拟身份前实例化一个Dummy对象,在模拟身份后再实例化另一个
  • 在Process Explorer的句柄视图中可观察到模拟身份时大量句柄被创建

TPL Dataflow 6.0.0和7.0.0版本均存在此问题,求修复/解决方法?

Dummy类代码

public class Dummy
{
    const int BATCH_SIZE    = 1_048_576;

    public Dummy()
    {
        DataflowLinkOptions linkOptions = new DataflowLinkOptions() { PropagateCompletion = true };

        TransformBlock<int, int> transformer = new TransformBlock<int, int>((int x) =>
            {
                return x - 1000;
            });

        BatchBlock<int> batcher = new BatchBlock<int>(BATCH_SIZE);

        ActionBlock<int[]> actioner = new ActionBlock<int[]>((int[] x) =>
            {
                Console.WriteLine("done");
            });
        
        transformer.LinkTo(batcher, linkOptions);
        batcher.LinkTo(actioner, linkOptions);

        for (int i = 0; i < 100_000_000; i++)
        {
            transformer.Post(i);
        }

        transformer.Complete();
        actioner.Completion.Wait();
        Console.WriteLine("done");
    }
}

编辑1:问题不限于TPL Dataflow

@theodor-zoulias的假设正确,将Dummy构造函数改为以下代码后,模拟身份时进程仍会占用大量Token句柄:

Parallel.For(0
            ,100_000
            ,(int i) =>
            {
                if (i % 1_000 == 0) { Console.WriteLine(i); }

                List<int> list = new List<int>(100_000);
                list.AddRange(Enumerable.Range(0, 100_000));
            });

演示应用中可观察到该行为。

编辑2:提供复现代码

已上传完整的复现应用代码。

编辑3:根因分析

通过dnSpy调试发现,根本原因是.NET Framework每次运行Task时都会复制Token句柄,用户代码几乎无法直接控制,但可能存在未知的解决机制。调用栈如下:

[pinvokes kernel32.dll DuplicateHandle()]
    mscorlib.dll!System.Security.Principal.WindowsIdentity.CreateFromToken(System.IntPtr userToken) (IL=0x003D, Native=0x00007FF8D33DBE20+0x122)
    mscorlib.dll!System.Security.Principal.WindowsIdentity.WindowsIdentity(System.IntPtr userToken, string authType, int isAuthenticated) (IL=0x0042, Native=0x00007FF8D33DBCD0+0x10E)
    mscorlib.dll!System.Security.Principal.WindowsIdentity.WindowsIdentity(Microsoft.Win32.SafeHandles.SafeAccessTokenHandle safeTokenHandle) (IL=0x000E, Native=0x00007FF8D33DCFE0+0x62)
    mscorlib.dll!System.Security.SecurityContext.CreateCopy() (IL=0x0052, Native=0x00007FF8D33E1CD0+0x11E)
    mscorlib.dll!System.Threading.ExecutionContext.CreateCopy() (IL≈0x007D, Native=0x00007FF8D33E19A0+0x224)
    mscorlib.dll!System.Threading.Tasks.Task.CopyExecutionContext(System.Threading.ExecutionContext capturedContext) (IL≈0x0013, Native=0x00007FF8D33B5260+0x6A)
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) (IL≈0x00AA, Native=0x00007FF8D33B3320+0x284)
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) (IL=0x0063, Native=0x00007FF8D33B3000+0xF4)
    mscorlib.dll!System.Threading.Tasks.ThreadPoolTaskScheduler.TryExecuteTaskInline(System.Threading.Tasks.Task task, bool taskWasPreviouslyQueued) (IL≈0x000F, Native=0x00007FF8D33B2F00+0x78)
    mscorlib.dll!System.Threading.Tasks.TaskScheduler.TryRunInline(System.Threading.Tasks.Task task, bool taskWasPreviouslyQueued) (IL≈0x004C, Native=0x00007FF8D33B26E0+0x143)
    mscorlib.dll!System.Threading.Tasks.Task.InternalRunSynchronously(System.Threading.Tasks.TaskScheduler scheduler, bool waitForCompletion) (IL≈0x0083, Native=0x00007FF8D33B2160+0x247)
    mscorlib.dll!System.Threading.Tasks.Task.RunSynchronously(System.Threading.Tasks.TaskScheduler scheduler) (IL=0x0016, Native=0x00007FF8D33B20C0+0x85)
    mscorlib.dll!System.Threading.Tasks.Parallel.ForWorker<object>(int fromInclusive, int toExclusive, System.Threading.Tasks.ParallelOptions parallelOptions, System.Action<int> body, System.Action<int, System.Threading.Tasks.ParallelLoopState> bodyWithState, System.Func<int, System.Threading.Tasks.ParallelLoopState, object, object> bodyWithLocal, System.Func<object> localInit, System.Action<object> localFinally) (IL=0x01A9, Native=0x00007FF8D33A1B20+0x69F)
    mscorlib.dll!System.Threading.Tasks.Parallel.For(int fromInclusive, int toExclusive, System.Action<int> body) (IL=0x001F, Native=0x00007FF8D33A0280+0xEA)
    ImpersonationTests.exe!Dummy.Dummy() (IL=0x0032, Native=0x00007FF8D33A0190+0xCA)

编辑4:线程数说明

我的实际应用(而非演示应用)线程数稳定在180-200左右,可通过Process Explorer观察到句柄持续增长的情况。

编辑5:评论回应

能否提供示例的内存转储以查看泄漏来源?
已上传内存转储相关内容。

并行for循环是否能正常终止?
可以,复现应用中Parallel.For在模拟身份前后都能正常运行完成。

实际代码未正确使用模拟身份
无法发布完整的企业应用代码,核心代码如下:

private async void Run_Click(object sender, EventArgs e)
{
    Tool t = new Tool();
    
    using (Utils u = new Utils())
    {
        try
        {
            u.BeginImpersonation();
            await Task.Run(() => t.RunPipeline());
        }
        finally
        {
            u.EndImpersonation();
        }
    }
}

这不是资源泄漏
请查看Process Explorer的屏幕录制,句柄数持续增长直至耗尽系统资源,属于明显的泄漏行为。

解决方案是为应用池使用AD账号
这是WinForms应用,不存在应用池。

可能代码运行在Linux上,需访问数据库或文件夹
我们使用Windows系统,模拟身份是为了让权限较低的用户访问数据库和UNC路径。目前我因自身权限足够,已注释掉模拟身份代码以正常运行应用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 07:55:57