神秘的“DataGrid的WinRT端口中没有足够的配额可用于处理此命令”
9月26日编辑
请参阅下面的完整背景。 tl; dr:数据网格控件导致了奇怪的exception,我正在寻找帮助,隔离原因并find解决scheme。
我已经进一步缩小了这一点。 我已经能够在一个更小的testing应用程序中重现行为,更可靠地触发了不稳定的行为。
我绝对可以排除线程和(我认为)内存问题。 新的应用程序不使用任务或其他线程/asynchronousfunction,我可以简单地触发未处理的exception,只需添加返回常量的属性到DataGrid中显示的对象的类。 这表明,问题是在非托pipe资源耗尽或我还没有想到的问题。
修改后的程序是这样构成的。 我创build了一个名为EntityCollectionGridView
的用户控件,它有一个标签和一个数据网格。 在控件的Loaded事件处理程序中,我将一个List<TestClass>
分配给1000或10000行的数据网格,让网格生成列。 此用户控件在页面的OnNavigatedTo
事件(或Loaded
,它似乎并不重要)的MainPage.xaml中实例化2-4次。 如果发生exception,则会在MainPage显示后立即发生。
有趣的是,这种行为看起来并不随所显示的行数而变化(它将可靠地工作10000行或者可靠地失败,每个网格中只有1000行),而是与所有网格中的总列数在给定的时间加载。 有20个属性显示,4个网格工作正常。 有35个属性和4个网格,抛出exception。 但是,如果我消除两个网格,35个属性相同的类将正常工作。
请注意,我添加到TestClass
从20跳到35列的所有属性的forms是:
public string StringXYZ { get { return "asdfasdfasdfasdfasf"; } }
所以,在后台数据中没有额外的内存(而且,我不认为内存压力是问题)。
你们都在想什么? 再次,任务pipe理器中的句柄/用户对象/等看起来不错,但有什么我可能会丢失?
原帖
我一直在研究Silverlight Toolkit DataGrid的一个端口到WinRT,在简单的testing(多种configuration和多达10000行)方面做得不错。 然而,正如我试图将其embedded到另一个WinRT应用程序,我已经遇到了一个零星的exception(typesSystem.Exception,在App.UnhandledException处理程序中引发),这是很难debugging。
Not enough quota is available to process this command. (Exception from HRESULT: 0x80070718)
错误始终可重现,但不是确定性的。 也就是说,我可以在每次运行应用程序时都发生这种情况,但是通过执行相同次数的相同步骤并不总是会发生。 该错误似乎发生在页面转换(无论是导航到新的页面,还是回到上一页),而不是(例如)更改DataGrid的ItemsSource时。
应用程序结构基本上是通过层次结构进行recursion访问,每个层次结构都显示一个页面。 在层次结构中的当前节点的页面上,显示每个子节点和一些孙节点,并且可以显示任何子节点的数据网格。 在实践中,我一直使用以下导航结构重现这一点:
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
一个典型的testing场景是,从Root开始,移到Child,移到Grandchild,移回到Child,然后当我尝试再次导航到Grandchild时,它会失败,并出现上面提到的exception。 但是,第一次打孙子可能会失败,或者在失败之前让我来回移动几次。
调用堆栈上只有一个托pipe框架,它是未处理的exception事件处理程序。 这是非常无益的。 切换到混合模式debugging,我得到以下内容:
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
这告诉我,不pipe我做错了什么,直到在应用程序的消息循环中至less有一个循环之后,才会注册(我还尝试使用“Debug | Exceptions …”打破所有抛出的exception – 尽可能我可以告诉,没有什么被投掷和吞下)。 我看到的有趣的堆栈框架是WindowProc
, OnReentrancyProtectedWindowMessage
和Tick
。 msg
是0x402(1026),这对我没有任何意义。 该页面列出了在以下上下文中使用的消息:
CBEM_SETIMAGELIST DDM_CLOSE DM_REPOSITION HKM_GETHOTKEY PBM_SETPOS RB_DELETEBAND SB_GETTEXTA TB_CHECKBUTTON TBM_GETRANGEMAX WM_PSD_MINMARGINRECT
…但这对我来说并不意味着什么(它甚至可能不相关)。
我能想出的三个理论是:
- 内存压力。 但是我已经用了24%的物理内存,而且这个应用程序的内存消耗less于100MB。 其他时候,应用程序将不会遇到任何问题一段时间的航行,并占用400MB的内存
- 线程问题,例如从工作线程访问UI线程。 事实上,我确实有后台线程上的数据访问。 但是这是使用一个在WinForms环境和Outlook插件中非常可靠的(移植的)框架,我认为线程的使用是安全的。 此外,我可以在这个应用程序中使用相同的数据,没有任何问题绑定到ListViews等等。 最后,configurationGrandchild节点,使得在第一个数据网格中select一行可以启动对第二个数据网格中显示的行的详细项目的请求(最初是空的,并且可以保留,而不会阻止exception)。 这种情况发生在没有页面转换的情况下,只要我select摆弄这个select,就可以完美地工作。 但是,导航返回到孩子可能会马上杀了我,即使在那个时候应该没有数据访问,因此不会线程操作,我知道。
- 某种资源枯竭 ,可能是GUI处理。 但我不认为我在这个系统上施加了太多的压力。 在一个执行中,与Tweetro相比,任何一个执行中的exception处理程序中,任务pipe理器报告进程使用662个句柄,21个用户对象和12个GDI对象,而Tweetro分别使用734,37和19。 我还有什么可以在这个类别中失踪?
我有足够的磁盘空间免费,并没有使用磁盘除了configuration文件以外的东西(和所有工作正常,在添加数据网格之前)。
我的下一个想法是尝试通过datagrid代码的一些潜在“有趣”部分,并跳过任何有疑问的部分。 我曾尝试与数据网格的ArrangeOverride,但exception似乎并不在乎我是否做到这一点。 另外,我不确定这是一个有用的策略。 由于在消息循环周期之后才会抛出exception,并且由于我无法确定何时会发生这种情况,所以我需要覆盖大量的排列,每个排列都会运行很多次,隔离问题代码。
debugging和发布模式都会引发错误。 而且,作为最后的背景说明,我们在这里处理的数据量很小,比我隔离的数据网格的10000行运行要小得多。 它可能是50-100行,也许30-40列。 而在抛出exception之前,数据和网格似乎工作并且响应良好。
所以,这就是为什么我来找你。 我的两个问题是:
- 错误信息是否给你提示什么可能是问题?
- 你将使用什么样的debugging策略来隔离问题代码?
非常感谢您提供任何帮助!
好吧, 在Tim Heuer [MSFT]的一些重要意见的input下 ,我想清楚发生了什么事以及如何解决这个问题。
令人惊讶的是,我的三个猜测没有一个是正确的。 这不是关于内存,线程或系统资源。 相反,它是关于Windows消息传递系统的限制。 显然,它有点像堆栈溢出exception,因为当你一次对可视化树进行了太多的改变时,asynchronous更新队列变得很长,导致跳线和exception被抛出。
在这种情况下,问题是有足够的UIElements进入我正在使用的数据网格,允许网格一次生成所有自己的列,可以在某些情况下超出限制。 我一次使用了大量的网格,并且所有加载都是为了响应页面导航事件,这使得我们需要花费更多的精力。
值得庆幸的是,我所遇到的局限性并不在于可视化树或XAML UI子系统本身,而仅仅是用于更新它的消息传递。 这意味着如果我可以在调度员的时钟的多个滴答点上展开相同的操作,我可以完成相同的最终结果。
我最终做的是指示我的数据网格不要自动生成自己的列。 相反,我将网格embedded到用户控件中,当数据被加载时,将parsing出所需的列并将其加载到列表中。 然后,我调用以下方法:
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
是一个简单的types,用于存放parsing出来的configuration或从文件加载的configuration。)
这个方法分别被下面的参数调用:列的列表为0,我的5的任意猜测是一次相当安全的列数。 但是这个数字是基于testing和期望可以同时加载大量的网格。 我向Tim询问了更多的信息,可以告知这部分过程,如果我更多地了解如何确定有多less是安全的,那么就会在这里报告。
在实践中,这似乎工作得很好,虽然它导致了你期望的渐进式渲染,并且列显着地popup。我期望这可以通过使用numToLoad
和其他UI numToLoad
的最大可能值得到numToLoad
-of手。 我可能调查隐藏网格,而列生成,只显示结果,当一切准备就绪。 最终,这个决定将会降低,感觉更“快速和stream畅”。
再次,我会更新这个答案与更多的信息,如果我得到它,但我希望这可以帮助未来面临类似问题的人。 在倾注了更多的时间之后,我不想让其他人为此而自杀。
看来这个问题已经在Windows 8.1预览版中解决了,我的应用程序redirect到了Windows 8.1。 我再也不能通过将数千种视觉材料扔到屏幕上来重现这个问题。