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

.NET Framework 4.8调用OpenFileDialog.ShowDialog异常卡顿及内存泄漏问题

.NET Framework 4.8 调用OpenFileDialog卡顿及内存泄漏问题

问题表现

在.NET Framework 4.8环境下调用OpenFileDialog.ShowDialog时出现无响应卡顿,同时内存占用持续攀升出现泄漏。即使内存涨到数GB,托管内存快照中也没有可观测的异常对象增长。

  • 对话框打开的初始目录文件数量极少,其他应用(如Visual Studio 2019)打开同目录的文件对话框无任何问题
  • 应用无异常定时任务逻辑,打开对话框的代码路径确认运行在UI线程,该功能已稳定运行多年,出现异常前仅在无关代码区域新增了报表打印功能

已完成排查进展

  • 系统仅安装了一个稳定运行多年的WinMerge Shell扩展
  • 虚拟机环境中运行该程序功能完全正常
  • 加载微软及NuGet官方符号文件后确认,泄漏发生在Windows非托管代码层:泄漏内存属于unresolved allocations类型,为持续增长的不透明void*内存地址列表,关联调用栈如下:
ntdll.dll!NtTraceEvent()  
ntdll.dll!RtlpLogHeapFreeEvent()  
ntdll.dll!RtlpFreeHeap()  
ntdll.dll!RtlpFreeHeapInternal()  
ntdll.dll!RtlFreeHeap()  
ntdll.dll!RtlDebugFreeHeap()  
ntdll.dll!RtlpFreeHeap()  
ntdll.dll!RtlpFreeHeapInternal()  
ntdll.dll!RtlFreeHeap()  
KernelBase.dll!LocalFree()  
ExplorerFrame.dll!CTContainer_PolicyLocalMem::DestroyMem(void *)  
ExplorerFrame.dll!TabletModeHelpers::GetMonitorConfig(int *, bool *)  
ExplorerFrame.dll!SHIsFileExplorerInTabletMode(void)  
ExplorerFrame.dll!UICheckbox::OnHosted()  
dui70.dll!DirectUI::Element::OnHosted()  
ExplorerFrame.dll!UIItem::OnHosted(class DirectUI::Element *)  
dui70.dll!DirectUI::Element::OnPropertyChanged()  
ExplorerFrame.dll!UIItem::OnPropertyChanged()  
dui70.dll!DirectUI::Element::_PostSourceChange()  
dui70.dll!DirectUI::Element::Insert()  
ExplorerFrame.dll!UICollection::_CreateAndInsertItems()  
ExplorerFrame.dll!UICollection::_RealizeItems()  
ExplorerFrame.dll!UICollection::RealizeSection(class SectionInfo *)  
ExplorerFrame.dll!LineScroller::_RealizeDirectionWorker()  
ExplorerFrame.dll!LineScroller::_RealizeDirectionWorker()  
ExplorerFrame.dll!LineScroller::_RealizeDirectionWorker()  
ExplorerFrame.dll!LineScroller::_RealizeAroundAnchor()  
ExplorerFrame.dll!LineScroller::_RealizeContent()  
ExplorerFrame.dll!LineScroller::_LayoutContent()  
ExplorerFrame.dll!LineScroller::_Viewer_SelfLayoutDoLayout(int,int)  
dui70.dll!DirectUI::Element::_FlushLayout()  
dui70.dll!DirectUI::Element::_FlushLayout()  
dui70.dll!DirectUI::Element::_FlushLayout()  
dui70.dll!DirectUI::Element::_FlushLayout()  
dui70.dll!DirectUI::Element::_FlushLayout()  
dui70.dll!DirectUI::DeferCycle::_EndDefer()  
dui70.dll!DirectUI::Element::EndDefer()  
dui70.dll!DirectUI::Element::_PostSourceChange()  
dui70.dll!DirectUI::Element::_SetValue()  
dui70.dll!DirectUI::Element::SetValue(struct DirectUI::PropertyInfo const * (*)(void),int,class DirectUI::Value *)  
ExplorerFrame.dll!LineViewer::SetLayoutState(int)  
ExplorerFrame.dll!LineScroller::_EnsureRealizePass(void)  
ExplorerFrame.dll!LineScroller::_OnLayoutChangeEvent(struct LayoutChangeEvent *)  
ExplorerFrame.dll!LineScroller::OnEvent()  
dui70.dll!DirectUI::Element::s_HandleDUIEventMessage(class DirectUI::Element *,struct EventMsg *)  
dui70.dll!DirectUI::Element::_DisplayNodeCallback()  
duser.dll!GPCB::xwInvokeDirect()  
duser.dll!SafeMsgQ::xwProcessNL()  
duser.dll!SafeMsgQ::xwProcessNL(void)  
duser.dll!CoreSC::xwProcessMsgQNL(void)  
duser.dll!CoreSC::xwProcessNL()  
duser.dll!MphProcessMessage()  
user32.dll!__ClientGetMessageMPH()  
ntdll.dll!KiUserCallbackDispatcherContinue()  
win32u.dll!NtUserPeekMessage()  
user32.dll!_PeekMessage()  
user32.dll!DialogBox2()  
user32.dll!InternalDialogBox()  
user32.dll!DialogBoxIndirectParamAorW()  
user32.dll!DialogBoxIndirectParamW()  
comdlg32.dll!<lambda>(void)()  
comdlg32.dll!CFileOpenSave::Show(struct HWND__*)  
[Managed to Native Transition]  
System.Windows.Forms.dll!System.Windows.Forms.FileDialog.RunDialogVista(System.IntPtr hWndOwner)  
System.Windows.Forms.dll!System.Windows.Forms.CommonDialog.ShowDialog(System.Windows.Forms.IWin32Window owner)  
Bingo.exe!OPEQ.frmBingo.tsmiFileOpen_Click(object sender, System.EventArgs e) Line 1098

关联业务代码如下,卡顿发生在openFileDialog1.ShowDialog(this)调用行:

private void tsmiFileOpen_Click(object sender, EventArgs e)
{
    Logging.Method();
    // 获取程序启动时启动的对话框过滤器描述检索任务
    // 如果任务失败或尚未完成,则使用初始值
    CultureInfo uiCulture = Thread.CurrentThread.CurrentUICulture;
    TaskParams openDialogFilterTaskParams;

    if (_tasks.TryGetValue(_fileDialogFilterTaskKey, out openDialogFilterTaskParams))
    {
        bool removeFromTasks = false;
        // 优先取任务结果,否则使用现有过滤器
        switch (openDialogFilterTaskParams.Task.Status)
        {
            case TaskStatus.RanToCompletion:
                // 更新过滤器变量
                var odfTask = (Task<IDictionary<CultureInfo, FileDialogFilterList>>)openDialogFilterTaskParams.Task;
                foreach (var kvPair in odfTask.Result)
                {
                    // 如果GetFileTypeName调用失败,返回的描述为null,此处做兼容处理
                    var updatedList = ReplaceNullDescriptions(kvPair.Value, kvPair.Key);
                    _openDialogFilterDict[kvPair.Key] = updatedList.CopyEmbellished().ToString();
                }
                removeFromTasks = true;
                break;
            case TaskStatus.Canceled:
            case TaskStatus.Faulted:
                removeFromTasks = true;
                // 使用过滤器初始值
                break;
            default:
                // 使用过滤器初始值
                break;
        }
        if (removeFromTasks)
        {
            try
            {
                // 此处主动调用Wait是为了避免将事件处理程序改为异步
                openDialogFilterTaskParams.Task.Wait(openDialogFilterTaskParams.TokenSource.Token);
            }
            catch(OperationCanceledException)
            {
                // 忽略取消异常
            }
            catch (AggregateException aggEx) when (aggEx.InnerExceptions.Any(ex => !(ex is OperationCanceledException)))
            {
                // 记录并忽略异常
                Logging.Exception(aggEx,
                    isRecoverableError: true,
                    includeStackTrace: true);
            }
            finally
            {
                openDialogFilterTaskParams.Task.Dispose();
                openDialogFilterTaskParams.TokenSource.Dispose();
                _tasks.TryRemove(_fileDialogFilterTaskKey, out _);
            }
        }
    }

    // 对话框会保证用户选择的文件存在,无需额外校验
    // 无需处理初始目录,对话框会自动处理
    openFileDialog1.Filter = _openDialogFilterDict[uiCulture];
    openFileDialog1.DefaultExt = _openDialogDefaultExtension;
    // 程序在下行代码处挂起
    if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
    {
        // 原子化处理任务创建、字典添加和检索
        var fileLoadTaskParams = _tasks.GetOrAdd(_fileLoadingTaskKey, (_, fnames) =>
        {
            TaskParams tParam = new TaskParams(
                null,
                null,
                new Progress<ReportLoaderProgressChangedEventArgs>());
            try
            {
                tParam.TokenSource = CancellationTokenSource.CreateLinkedTokenSource(_tidyShutdownCts.Token);
                tParam.Task = new ReportLoader(fnames).LoadAsync(tParam.TokenSource.Token, (IProgress<ReportLoaderProgressChangedEventArgs>)tParam.Additional);
            }
            catch
            {
                tParam.TokenSource?.Dispose();
                tParam.Task?.Dispose();
                throw;
            }
            return tParam;
        }, openFileDialog1.FileNames.AsEnumerable());

        // 设置默认扫描模式
        // 加载多个文件时为搜索模式,仅加载一个文件时为校验模式
        _scanModeSelected = openFileDialog1.FileNames.Length > 1
            ? tsBtnScanType_Search
            : tsBtnScanType_Verification;

        /* 将获取到的参数传递给LoadFile,LoadFile负责向用户展示非关键异常
         * 严重异常会从该方法抛出到FireAndForget,此时允许应用退出
         */
        LoadFile(
            (Task<OPEQReportDataSet>)fileLoadTaskParams.Task,
            fileLoadTaskParams.TokenSource,
            (Progress<ReportLoaderProgressChangedEventArgs>)fileLoadTaskParams.Additional)
            .FireAndForget(null, null, () =>
            {
                fileLoadTaskParams.Task.Dispose();
                fileLoadTaskParams.TokenSource?.Dispose();
                _tasks.TryRemove(_fileLoadingTaskKey, out _);
            });
    } 
}

最新进展

程序已无征兆恢复正常,推测问题与Windows系统更新相关。

排查思路参考

  1. 系统补丁校验:从调用栈可以看出泄漏出现在ExplorerFrame.dll的平板模式检测逻辑中,可先排查最近安装的Windows累积更新,尤其是涉及文件资源管理器、平板模式适配的补丁,卸载可疑更新后复测
  2. Shell扩展兼容验证:虽然WinMerge已稳定运行多年,仍可临时禁用所有非微软Shell扩展,确认是否是扩展与新系统补丁兼容问题导致的异常
  3. 对话框降级适配:调用OpenFileDialog前设置AutoUpgradeEnabled = false,强制使用XP风格的旧版文件对话框,绕过基于DirectUI实现的新版对话框逻辑,验证是否能规避问题
  4. 系统状态重置:检查系统平板模式开关是否处于异常状态、多显示器配置是否有异常,可尝试重置文件资源管理器设置、重启桌面窗口管理器(dwm.exe)后复测

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 14:36:00