在DataGrid的WinRT端口中,神秘的“没有足够的配额可用于处理此命令”

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在DataGrid的WinRT端口中,神秘的“没有足够的配额可用于处理此命令”相关的知识,希望对你有一定的参考价值。

编辑9月26日

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

我把它缩小了一点。我已经能够在较小的测试应用程序中重现行为,更可靠地触发不稳定的行为。

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

修订后的计划结构如下。我创建了一个名为EntityCollectionGridView的用户控件,它有一个标签和一个数据网格。在控件的Loaded事件处理程序中,我将List<TestClass>分配给具有1000或10000行的数据网格,让网格生成列。这个用户控件在页面的OnNavigatedTo事件中的MainPage.xaml中被实例化了2-4次(或者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 ...”打破所有抛出的异常 - 至于我可以说,没有任何东西被扔掉和吞没)。我看到的有趣的堆栈帧是WindowProcOnReentrancyProtectedWindowMessageTickmsg是0x402(1026),这对我没有任何意义。 This page列出了以下上下文中使用的消息:

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等问题。最后,配置孙子节点,使得在第一个数据网格中选择一行开始对行的详细项目的请求,该请求显示在第二个数据网格中(最初为空,并且可以保持不变,而不会阻止异常)。这种情况在没有页面过渡的情况下发生,并且只要我选择摆弄选择就可以完美地工作。但导航回Child可能会立即杀死我,即使此时应该没有数据访问权限,因此也不会对我所知道的操作进行操作。
  3. 某种资源耗尽,可能是GUI处理。但我不认为我对这个系统施加了太大的压力。在一次执行中,打破异常处理程序,任务管理器使用662个句柄,21个用户对象和12个GDI对象报告进程,而Tweetro则分别使用734,37和19而没有问题。在这个类别中我还有什么可能遗漏的?

我有足够的磁盘空间,并且除了配置文件以外我没有使用磁盘(并且在添加datagrids之前所有工作正常)。

我的下一个想法是尝试逐步浏览数据网格代码的一些潜在的“有趣”部分,并跳过任何有问题的部分。我确实尝试过使用datagrid的ArrangeOverride,但异常并不关心我是否这样做。此外,我不确定这是一个有用的策略。由于异常不会在消息循环的一个循环之后被抛出,并且因为我无法确定它何时会发生,所以我需要覆盖大量的排列,运行每个排列很多次,隔离问题代码。

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

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

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

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

答案

好吧,有一些critical input from 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作为一次加载的相当安全的列数;但是这个数字是基于测试和期望可以同时加载大量网格。我向Tim询问了可能为这部分流程提供信息的更多信息,如果我了解了如何确定安全性的更多信息,我会在此回复。

在实践中,这似乎可以充分发挥作用,虽然它会产生你期望的渐进式渲染,并且列明显突然出现。我希望通过使用numToLoad的最大可能值和其他UI技巧可以改善这一点。 -of手。我可能会在生成列时调查隐藏网格,并仅在所有内容都准备就绪时显示结果。最终,这个决定将归结为更“快速和流畅”的决定。

如果我得到它,我会再次提供更多信息来更新这个答案,但我希望这可以帮助将来遇到类似问题的人。在倾注了更多的时间,而不是我愿意承认这个bug,我不希望任何其他人为此而自杀。

另一答案

在重新定位我的Windows 8.1应用程序后,似乎在Windows 8.1 Preview中修复了此问题。我不能再通过将数千个视觉效果转储到屏幕上来重新创建此问题。

以上是关于在DataGrid的WinRT端口中,神秘的“没有足够的配额可用于处理此命令”的主要内容,如果未能解决你的问题,请参考以下文章

UWP 社区工具包 DataGrid onecoreuap 错误

MKV 文件的开源 WinRT 库

在 WinRT 中绘制二叉树

如何在 WinRT 中获取文件大小?

没有锁屏的时间触发后台任务。(WinRT)

使用 WinRT Appointments API 在没有用户确认的情况下编写约会