45

编辑 9 月 26 日

有关完整背景,请参见下文。tl; dr:数据网格控件导致奇怪的异常,我正在寻求帮助隔离原因并找到解决方案。

我进一步缩小了这个范围。我已经能够在较小的测试应用程序中重现该行为,并且可以更可靠地触发不稳定的行为。

我绝对可以排除线程和(我认为)内存问题。新应用程序不使用任务或其他线程/异步功能,我只需向 DataGrid 中显示的对象类添加返回常量的属性即可触发未处理的异常。这向我表明问题出在非托管资源耗尽或我还没有想到的问题上。

修改后的程序结构是这样的。我创建了一个名为的用户控件EntityCollectionGridView,它有一个标签和一个数据网格。在控件的 Loaded 事件处理程序中,我将 a 分配给List<TestClass>具有 1000 或 10000 行的数据网格,让网格生成列。此用户控件在页面事件中的 MainPage.xaml 中实例化 2-4 次OnNavigatedTo(或者Loaded,这似乎无关紧要)。如果发生异常,则会在 MainPage 显示后立即发生。

有趣的是,行为似乎并没有随着显示的行数而变化(它会在 10000 行时可靠地工作,或者在每个网格中只有 1000 行时可靠地失败),而是随着所有网格中的列总数而变化在给定时间加载。显示 20 个属性,4 个网格可以正常工作。使用 35 个属性和 4 个网格,会引发异常。但是如果我消除两个网格,具有 35 个属性的同一个类将正常工作。

请注意,我添加以TestClass从 20 列跳转到 35 列的所有属性均采用以下形式:

public string StringXYZ { get { return "asdfasdfasdfasdfasf"; } }

因此,支持数据中没有额外的内存(同样,我不认为内存压力是问题)。

大家怎么看?同样,任务管理器中的句柄/用户对象/等看起来不错,但是我可能还缺少其他东西吗?

原帖

我一直致力于将 Silverlight Toolkit DataGrid 移植到 WinRT,它在简单的测试(各种配置和多达 10000 行)中做得很好。但是,当我尝试将它嵌入到另一个 WinRT 应用程序时,我遇到了一个零星的异常(System.Exception 类型,在 App.UnhandledException 处理程序中引发),这证明很难调试。

Not enough quota is available to process this command. (Exception from HRESULT: 0x80070718)

错误始终是可重现的,但不是确定性的。也就是说,我可以在每次运行应用程序时实现它,但它并不总是通过执行相同的确切步骤集相同的次数来实现。该错误似乎发生在页面转换上(无论是向前导航到新页面还是返回到前一页),而不是(例如)在更改数据网格的 ItemsSource 时发生。

应用程序结构基本上是通过层次结构递归访问,每个层次结构级别显示一个页面。在层次结构中当前节点的页面上,显示了每个子节点和一些孙节点,并且可以显示任何子节点的数据网格。在实践中,我始终使用以下导航结构重现这一点:

Root page: shows no datagrid
  Child page: shows one datagrid and a few listviews
    Grandchild page: shows two datagrids, one bound to the
                     same source as Child page, the other one empty

一个典型的测试场景是,从 Root 开始,移动到 Child,移动到 Grandchild,移动回 Child,然后当我再次尝试导航到 Grandchild 时,它失败了,除了我上面提到的异常。但它可能会在我第一次击中孙子时失败,或者它可能让我在失败之前来回移动几次。

调用堆栈上只有一个托管帧,即未处理的异常事件处理程序。这是非常无益的。切换到混合模式调试,我得到以下信息:

WinRTClient.exe!WinRTClient.App.InitializeComponent.AnonymousMethod__14(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) Line 50 + 0x20 bytes  C#
[Native to Managed Transition]  
Windows.UI.Xaml.dll!DirectUI::CFTMEventSource<Windows::UI::Xaml::IUnhandledExceptionEventHandler,Windows::UI::Xaml::IApplication,Windows::UI::Xaml::IUnhandledExceptionEventArgs>::Raise(Windows::UI::Xaml::IApplication * pSource, Windows::UI::Xaml::IUnhandledExceptionEventArgs * pArgs)  Line 327  C++
Windows.UI.Xaml.dll!DirectUI::Application::RaiseUnhandledExceptionEventHelper(long hrEncountered, unsigned short * pszErrorMessage, unsigned int * pfIsHandled)  Line 920 + 0xa bytes   C++
Windows.UI.Xaml.dll!DirectUI::ErrorHelper::CallAUHandler(unsigned int errorCode, unsigned int * pfIsHandled, wchar_t * * pbstrErrorMessage)  Line 39 + 0x14 bytes   C++
Windows.UI.Xaml.dll!DirectUI::ErrorHelper::ProcessUnhandledErrorForUserCode(long error)  Line 82 + 0x10 bytes   C++
Windows.UI.Xaml.dll!AgCoreCallbacks::CallAUHandler(unsigned int errorCode)  Line 1104 + 0x8 bytes   C++
Windows.UI.Xaml.dll!CCoreServices::ReportUnhandledError(long errorXR)  Line 6582    C++
Windows.UI.Xaml.dll!CXcpDispatcher::Tick()  Line 1126 + 0xb bytes   C++
Windows.UI.Xaml.dll!CXcpDispatcher::OnReentrancyProtectedWindowMessage(HWND__ * hwnd, unsigned int msg, unsigned int wParam, long lParam)  Line 653 C++
Windows.UI.Xaml.dll!CXcpDispatcher::WindowProc(HWND__ * hwnd, unsigned int msg, unsigned int wParam, long lParam)  Line 401 + 0x24 bytes    C++
user32.dll!_InternalCallWinProc@20()  + 0x23 bytes  
user32.dll!_UserCallWinProcCheckWow@36()  + 0xbd bytes  
user32.dll!_DispatchMessageWorker@8()  + 0xf8 bytes 
user32.dll!_DispatchMessageW@4()  + 0x10 bytes  
Windows.UI.dll!Windows::UI::Core::CDispatcher::ProcessMessage(int bDrainQueue, int * pbAnyMessages)  Line 121   C++
Windows.UI.dll!Windows::UI::Core::CDispatcher::ProcessEvents(Windows::UI::Core::CoreProcessEventsOption options)  Line 184 + 0x10 bytes C++
Windows.UI.Xaml.dll!CJupiterWindow::RunCoreWindowMessageLoop()  Line 416 + 0xb bytes    C++
Windows.UI.Xaml.dll!CJupiterControl::RunMessageLoop()  Line 714 + 0x5 bytes C++
Windows.UI.Xaml.dll!DirectUI::DXamlCore::RunMessageLoop()  Line 2539 + 0x5 bytes    C++
Windows.UI.Xaml.dll!DirectUI::FrameworkView::Run()  Line 91 C++
twinapi.dll!`Windows::ApplicationModel::Core::CoreApplicationViewAgileContainer::RuntimeClassInitialize'::`55'::<lambda_A2234BA2CCD64E2C>::operator()(void * pv)  Line 560  C++
twinapi.dll!`Windows::ApplicationModel::Core::CoreApplicationViewAgileContainer::RuntimeClassInitialize'::`55'::<lambda_A2234BA2CCD64E2C>::<helper_func>(void * pv)  Line 613 + 0xe bytes   C++
SHCore.dll!_SHWaitForThreadWithWakeMask@12()  + 0xceab bytes    
kernel32.dll!@BaseThreadInitThunk@12()  + 0xe bytes 
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes   
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes    

这向我表明,无论我做错了什么,直到在应用程序的消息循环中至少一个循环之后才会注册(我还尝试使用“Debug | Exceptions ...”打破所有抛出的异常 - 就我可以说,没有东西被扔掉和吞下)。我看到的有趣的堆栈帧是WindowProcOnReentrancyProtectedWindowMessageTick。是 0x402 (1026),这msg对我来说没有任何意义。此页面列出了在以下上下文中使用的消息:

CBEM_SETIMAGELIST 
DDM_CLOSE 
DM_REPOSITION 
HKM_GETHOTKEY 
PBM_SETPOS 
RB_DELETEBAND 
SB_GETTEXTA 
TB_CHECKBUTTON 
TBM_GETRANGEMAX 
WM_PSD_MINMARGINRECT

...但这对我来说也没什么意义(它甚至可能不相关)。

我能想到的三个理论是:

  1. 内存压力。但是我遇到了这个问题,我有 24% 的物理内存可用,而应用程序消耗的内存不到 100MB。其他时候,该应用程序在导航一段时间并占用 400MB 内存时不会遇到任何问题
  2. 线程问题,例如从工作线程访问 UI 线程。而且,事实上,我确实在后台线程上进行了数据访问。但这是使用在 WinForms 环境和 Outlook 插件中非常可靠的(移植的)框架,我认为线程使用是安全的。此外,我可以在这个应用程序中使用相同的数据,而不会出现任何问题,只是绑定到 ListViews 等等。最后,配置 Grandchild 节点,以便在第一个数据网格中选择一行会启动对该行的详细信息项的请求,该请求将显示在第二个数据网格中(最初是空的,并且可以保持不变而不会阻止异常)。这发生在没有页面转换的情况下,只要我选择摆弄选择,它就可以完美地工作。但是导航回 Child 可能会立即杀死我,
  3. 某种资源耗尽,可能是 GUI 句柄。但我不认为我对这个系统施加了那么大的压力。在一次执行中,中断异常处理程序,任务管理器报告使用 662 个句柄、21 个用户对象和 12 个 GDI 对象的进程,而 Tweetro 分别使用 734、37 和 19 个没有问题。在这个类别中我还可能缺少什么?

我有足够的可用磁盘空间,并且除了配置文件之外,我没有将磁盘用于任何其他内容(并且在添加数据网格之前一切正常)。

我的下一个想法是尝试遍历数据网格代码中一些潜在的“有趣”部分,并跳过任何有问题的部分。我确实尝试过使用数据网格的 ArrangeOverride,但异常似乎并不关心我是否这样做。另外,我不确定这是一个有用的策略。由于直到消息循环的一个循环之后才会引发异常,并且由于我无法确定它何时会发生,所以我需要涵盖大量的排列,运行每个排列大量次,隔离问题代码。

在调试和发布模式下都会引发错误。而且,作为最后的背景说明,我们在这里处理的数据量很小,比我单独运行的 10000 行数据网格要小得多。它可能大约有 50-100 行,可能有 30-40 列。在抛出异常之前,数据和网格似乎可以正常工作并且响应良好。

所以,这就是我来找你的原因。我的两个问题是:

  1. 错误信息是否为您提供了有关可能是什么问题的任何提示?
  2. 您将使用什么调试策略来隔离问题代码?

非常感谢您提供的任何帮助!

4

2 回答 2

57

好的,通过Tim Heuer [MSFT] 的一些关键意见,我弄清楚了发生了什么以及如何解决这个问题。

令人惊讶的是,我最初的三个猜测都不是正确的。这与内存、线程或系统资源无关。相反,它是关于 Windows 消息传递系统的限制。显然,它有点像堆栈溢出异常,因为当您一次对可视化树进行太多更改时,异步更新队列会变得很长,以至于它会跳线并抛出异常。

在这种情况下,问题是有足够的 UIElements 进入我正在使用的数据网格,从而允许网格一次生成所有自己的列,在某些情况下可能会超过限制。我同时使用了多个网格,并且所有的加载都是为了响应页面导航事件,这使得确定它变得更加棘手。

值得庆幸的是,我遇到的限制不是可视化树或 XAML UI 子系统本身的限制,只是用于更新它的消息传递。这意味着,如果我可以在调度程序时钟的多个滴答声中展开相同的操作,我可以完成相同的最终结果。

我最终做的是指示我的数据网格不要自动生成自己的列。相反,我将网格嵌入到用户控件中,当加载数据时,它会解析出所需的列并将它们加载到列表中。然后,我调用了以下方法:

void LoadNextColumns(List<ColumnDisplaySetup> colDef, int startIdx, int numToLoad)
{
    for (int idx = startIdx; idx < startIdx + numToLoad && idx < colDef.Count; idx++)
    {
        DataGridTextColumn newCol = new DataGridTextColumn();
        newCol.Header = colDef[idx].Header;
        newCol.Binding = new Binding() { Path = new PropertyPath(colDef[idx].Property) };
        dgMainGrid.Columns.Add(newCol);
    }

    if (startIdx + numToLoad < colDef.Count)
    {
        Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                    LoadNextColumns(colDef, startIdx + numToLoad, numToLoad);
            });
    }
}

ColumnDisplaySetup是一种用于容纳解析出的配置或从文件加载的配置的普通类型。)

分别使用以下参数调用此方法:列列表,0,以及我任意猜测的 5 作为一次加载的相当安全的列数;但是这个数字是基于测试和预期可以同时加载大量网格的。我向蒂姆询问了更多信息,这些信息可能会为这部分过程提供信息,如果我了解更多关于如何确定多少安全的信息,我会在这里报告。

在实践中,这似乎工作得很好,尽管它会导致您期望的那种渐进式渲染,并且列明显弹出。我希望这可以通过使用最大可能值numToLoad和其他 UI 技巧来改善 -手。我可能会研究在生成列时隐藏网格,并且仅在一切准备就绪时才显示结果。最终决定将归结为哪个感觉更“快速和流畅”。

同样,如果我得到它,我会用更多信息更新这个答案,但我希望这可以帮助将来面临类似问题的人。在投入比我愿意承认的更多的时间来寻找错误之后,我不希望其他人为此而自杀。

于 2012-09-27T04:37:13.943 回答
0

在将我的应用程序重新定位到 Windows 8.1 之后,此问题似乎已在 Windows 8.1 Preview 中得到修复。我无法再通过将数千个视觉效果放到屏幕上来重现这个问题。

于 2013-07-23T15:23:59.687 回答