使用Microsoft远程桌面客户端时出现句柄泄漏问题求助
AxMSTSCLib.dll嵌入WPF时的句柄泄漏问题
我正在使用AxMSTSCLib.dll将MSTSC客户端嵌入WPF控件,功能运行正常,但存在句柄泄漏问题:每次成功执行Connect-Disconnect循环后,Process.HandleCount会持续增长,且这些句柄无法在运行时被垃圾回收。仅在成功建立连接时才会发生泄漏,因此推测存在残留的文件句柄或TCP流,但难以定位、访问和操作这些非托管资源。
复现代码
搭建核心项目步骤:
- 打开Visual Studio,创建名为“RemoteClientHandleLeak”的WPF App (.Net Framework)项目
- 添加4个引用:
- 程序集:System.Windows.Forms和WindowsFormsIntegration
- 浏览引用:位于
C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Simulator\14.0\AxMSTSCLib.dll的AxMSTSCLib.dll和MSTSCLib.dll,并将它们的“Embed Interop Types”属性设置为False(这会自动将“Copy Local”设为True)
- 将以下代码粘贴到MainWindow.xaml:
<Window x:Class="RemoteClientHandleLeak.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="830" Height="535" > <Grid x:Name="LayoutRoot" Background="#cccdd1"> <Button x:Name="Start" Content="Start" Click="Click_StartSequence" Margin="10,3,685,466" /> <Button x:Name="Stop" Content="Stop" Click="Click_StopSequence" Margin="274,3,421,466" /> <Button x:Name="Probe" Content="Probe Handles" Click="Click_Probe" Margin="496,3,199,466" /> <Button x:Name="ForceGC" Content="Force GC" Click="Click_ForceGC" Margin="628,3,67,466" /> <Button x:Name="StartAlt" Content="Start (no connect)" Click="Click_StartSequenceAlt" Margin="142,3,553,466" /> <Grid x:Name="parentGrid" Width="800" Height="450" Margin="10,42,10,10"/> </Grid> </Window>
- 将以下代码粘贴到MainWindow.xaml.cs:
using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Windows; using System.Text; using RdpClient = AxMSTSCLib.AxMsRdpClient7NotSafeForScripting; using FormHost = System.Windows.Forms.Integration.WindowsFormsHost; namespace RemoteClientHandleLeak { public partial class MainWindow : Window { private string memLogPath = @"C:\Marrt\logWpf_RemoteDesktopClient.txt"; private const string srv = "192.168.1.15", usr = "remote", pwd = "remote"; private int connectionDurationMs = 200; private bool trackDisposalState = true; List<WeakReference> hostHistory = new List<WeakReference>(); List<WeakReference> clientHistory = new List<WeakReference>(); public async Task MySequence() { for (int i = 0; i < 10; i++) { var task1 = LeakDemonstration(); await Task.WhenAll(task1); ProbeWeakRef(); MemoryStamp(); if (stop) { return; } } } public async Task LeakDemonstration() { using (var winFormHost = new FormHost()) using (var axMsRdpClient = new RdpClient()) { if(trackDisposalState){ hostHistory.Add(new WeakReference(winFormHost, false)); clientHistory.Add(new WeakReference(axMsRdpClient, false)); } winFormHost.Child = axMsRdpClient; parentGrid.Children.Add(winFormHost); ApplyClientSetting(axMsRdpClient, (int)parentGrid.ActualWidth, (int)parentGrid.ActualHeight); if (!skipConnect){ //LEAKING HANDLES! axMsRdpClient.Connect(); await Task.Delay(connectionDurationMs); axMsRdpClient.Disconnect(); } else { //no leak, perfectly clean disposal await Task.Delay(connectionDurationMs); } await Task.Delay(120); parentGrid.Children.Clear();// same as parentGrid.Children.Remove(winFormHost); } } private void ApplyClientSetting(RdpClient client, int w, int h) { //client.Domain = ""; client.Server = srv; client.UserName = usr; client.AdvancedSettings2.ClearTextPassword = pwd; client.AdvancedSettings7.EnableCredSspSupport = true; //client.ColorDepth = 16; client.DesktopWidth = w; client.DesktopHeight = h; client.ConnectingText = "Connecting..."; client.ConnectedStatusText = "Connected"; client.DisconnectedText = "Offline"; } #region Helper & UI private void ProbeWeakRef() { int hCount = hostHistory.Count, cCount = clientHistory.Count, hAlive = 0, cAlive = 0; for (int i = 0; i < hostHistory.Count; i++) { hAlive += hostHistory[i].IsAlive ? 1 : 0; cAlive += hostHistory[i].IsAlive ? 1 : 0; } using (var process = System.Diagnostics.Process.GetCurrentProcess()) { Console.WriteLine($"WeakRefs: host.IsAlive:{hAlive}/{hCount} client.IsAlive:{cAlive}/{cCount}; HandleCount:{process.HandleCount}"); } } public void MemoryStamp() { using (var process = System.Diagnostics.Process.GetCurrentProcess()) { StringBuilder sb = new StringBuilder(); sb.Append($"{ DateTime.Now.ToString("yyyy-MM-dd\tHH:mm:ss.ffff")}\tMemory\t"); sb.Append($"{process.PeakWorkingSet64}\t{process.PrivateMemorySize64}\t{process.WorkingSet64}\t{GC.GetTotalMemory(true).ToString("#.,###")}\t{process.HandleCount}\n"); System.IO.File.AppendAllText( memLogPath, sb.ToString()); //Console.WriteLine(sb.ToString()); sb.Clear(); } } private void ForceGarbageCollection() { GC.Collect(); GC.WaitForPendingFinalizers(); ProbeWeakRef(); } public MainWindow() { InitializeComponent(); } private bool stop; private bool skipConnect; private void Click_StartSequence(object sender, RoutedEventArgs e) { stop = false; skipConnect = false; Task seq = MySequence(); } private void Click_StartSequenceAlt(object sender, RoutedEventArgs e) { stop = false; skipConnect = true; Task seq = MySequence(); } private void Click_StopSequence(object sender, RoutedEventArgs e) { stop = true; } private void Click_Probe(object sender, RoutedEventArgs e) { ProbeWeakRef(); } private void Click_ForceGC(object sender, RoutedEventArgs e) { ForceGarbageCollection(); } #endregion Helper & UI } }
- 修改连接设置(主机需可访问且登录信息有效才会出现泄漏)
- 点击Start按钮(执行10次Connect-Disconnect循环),观察句柄泄漏情况
已尝试的无效解决方案
- 纯WinForm项目(无异步自动化函数)
- 调整多种AdvancedSettings、AdvancedSettings2-7配置
- 使用Visual Studio内存分析器对比快照(提示存在TCP流句柄)
- 手动调用Dispose释放资源
- 尝试关闭句柄
- 对多个对象调用
Marshal.ReleaseComObject(Object) - 尝试Object.Finalize()模式(不确定实现是否正确)
- 在
rdp.GetOcx()上调用ResourceManager.ReleaseAllResources() - 连接其他操作系统(Unix上的xrdp)
- 查阅相关官方文档
- 尚未尝试AppDomain重载
环境:Windows 10,.Net Framework 4.7.2
内容的提问来源于stack exchange,提问作者Marrt
相关产品推荐
相关产品推荐

