神秘的“没有足够的配额可用于处理此命令"在 DataGrid 的 WinRT 端口中 [英] Mysterious "Not enough quota is available to process this command" in WinRT port of DataGrid

查看:28
本文介绍了神秘的“没有足够的配额可用于处理此命令"在 DataGrid 的 WinRT 端口中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑 9 月 26 日

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

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

I've narrowed this down a bit further. I have been able to reproduce the behavior in a smaller test app with more reliable triggering of the erratic behavior.

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

I can definitely rule out both threading and (I think) memory issues. The new app uses no Tasks or other threading/asynchronous features, and I can trigger the unhandled exception simply by adding properties that return a constant to the class of objects shown in the DataGrid. This indicates to me that the problem is either in unmanaged resource exhaustion or something I haven't thought of yet.

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

The revised program is structured like this. I have created a user control called EntityCollectionGridView which has a label and a data grid. In the control's Loaded event handler, I assign a List<TestClass> to the data grid with 1000 or 10000 rows, letting the grid generate the columns. This user control is instantiated 2-4 times in MainPage.xaml in the page's OnNavigatedTo event (or Loaded, it doesn't seem to matter). If an exception occurs, it occurs immediately after MainPage is shown.

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

The interesting thing is, the behavior doesn't seem to vary with the number of rows shown (it will work reliably with 10000 rows or fail reliably with only 1000 rows in each grid) but rather with the total number of columns in all the grids loaded at a given time. With 20 properties to show, 4 grids works fine. With 35 properties and 4 grids, the exception is thrown. But if I eliminate two grids, the same class with 35 properties will work fine.

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

Note that all of the properties I add to TestClass to jump from 20 to 35 columns are of the form:

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

因此,后备数据中没有额外的内存(而且,无论如何,我认为内存压力不是问题).

So, there's no additional memory in the backing data (and again, I don't think memory pressure is the problem anyway).

大家怎么看?同样,任务管理器中的句柄/用户对象/等看起来不错,但还有什么我可能遗漏的吗?

What do you all think? Again, the handles/user objects/etc in Task Manager look good, but is there something else I might be missing?

原帖

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

I have been working on a port of the Silverlight Toolkit DataGrid to WinRT, and it has done well enough in simple tests (a variety of configurations and up to 10000 rows). However, as I have tried to embed it into another WinRT app I have run into a sporadic exception (of type System.Exception, raised in the App.UnhandledException handler) that is proving very difficult to debug.

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

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

The error is consistently reproducible, but not deterministically. That is, I can make it happen every time I run the App, but it doesn't always happen by performing the same exact set of steps the same number of times. The error seems to occur on page transitions (whether navigating to a new page forward or back to a previous page), and not (for instance) when changing the ItemsSource of the datagrid.

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

The application structure is basically recursive access through a hierarchy, with a page shown at each hierarchy level. On the page for the current node in the hierarchy, each child node and some grandchild nodes are shown, and for any subnode a datagrid may be shown. In practice, I consistently reproduce this with the following navigation structure:

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 时,它失败了,除了我上面提到的例外.但它可能会在我第一次打孙子时失败,或者它可能让我来回移动几次才失败.

A typical test scenario is, start at Root, move to Child, move to Grandchild, move back to Child, and then when I try to navigate to Grandchild again, it fails with the exception I mentioned above. But it might fail the first time I hit Grandchild, or it might let me move back and forth a few times before failing.

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

The call stack has only one managed frame on it, which is the unhandled exception event handler. This is very unhelpful. Switching to mixed mode debugging, I get the following:

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    

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

This indicates to me that whatever I'm doing wrong doesn't register until after at least one cycle in the app's message loop (and I also tried breaking on all thrown exceptions using "Debug | Exceptions..." -- as far as I can tell, nothing is thrown and swallowed). The interesting stack frames I see are WindowProc, OnReentrancyProtectedWindowMessage, and Tick. The msg is 0x402 (1026), which doesn't mean anything to me. This page lists that message as used in the following contexts:

CBEM_SETIMAGELIST 
DDM_CLOSE 
DM_REPOSITION 
HKM_GETHOTKEY 
PBM_SETPOS 
RB_DELETEBAND 
SB_GETTEXTA 
TB_CHECKBUTTON 
TBM_GETRANGEMAX 
WM_PSD_MINMARGINRECT

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

...but that doesn't mean anything much to me, either (it might not even be relevant).

我能提出的三个理论是:

The three theories I can come up with are these:

  1. 内存压力.但我遇到了这个问题,我的物理内存有 24% 可用,并且应用程序消耗的内存不到 100MB.其他时候,该应用在导航一段时间和占用 400MB 内存时不会遇到任何问题
  2. 线程问题,例如从工作线程访问 UI 线程.而且,事实上,我确实在后台线程上进行了数据访问.但这是使用在 WinForms 环境和 Outlook 插件中非常可靠的(移植)框架,我认为线程使用是安全的.此外,我可以在这个应用程序中使用相同的数据,而不会出现任何绑定到 ListViews 等的问题.最后,孙节点被配置为选择第一个数据网格中的一行会启动对该行详细信息项的请求,这些项显示在第二个数据网格中(它最初是空的,并且可以在不阻止异常的情况下保持如此).这发生在没有页面转换的情况下,只要我选择摆弄选择,就可以完美地工作.但是导航回 Child 可能会立即杀死我,即使此时应该没有数据访问,因此也没有我所知道的线程操作.
  3. 资源耗尽某种类型的,也许是 GUI 句柄.但我认为我没有给这个系统施加太大的压力.在一次执行中,中断异常处理程序,任务管理器报告使用 662 个句柄、21 个用户对象和 12 个 GDI 对象的进程,而 Tweetro 分别使用 734、37 和 19 个没有问题.此类别中我还缺少什么?
  1. Memory pressure. But I have run into this with 24% of my physical memory free and the app consuming less than 100MB of memory. Other times, the app won't have hit any problems navigating around a while and racking up 400MB of memory
  2. Threading problems, such as access to the UI thread from a worker thread. And, in fact, I do have data access happening on a background thread. But this is using a (ported) framework that has been very reliable in a WinForms environment and in an Outlook plugin, and I think the thread use is safe. Additionally, I can use the same data in this app without any problems binding just to ListViews and so forth. Finally, the Grandchild node is configured such that selecting a row in the first datagrid kicks off a request for the row's detail items, which are displayed in the second datagrid (which is initially empty, and can remain so without preventing the exception). This happens without a page transition and works flawlessly for as long as I choose to fiddle with the selection. But navigating back to Child might kill me right away, even though there should be no data access at that point and therefore not threading operations that I know of.
  3. Resource exhaustion of some kind, maybe GUI handles. But I don't think I'm putting that much pressure on this system. In one execution, breaking in the exception handler, Task Manager reports the process using 662 handles, 21 User objects, and 12 GDI objects, as compared to Tweetro which is using 734, 37, and 19 respectively without problems. What else might I be missing in this category?

我有足够的可用磁盘空间,无论如何我不会将该磁盘用于配置文件以外的任何内容(并且在添加数据网格之前一切正常).

I have plenty of disk space free, and am not using the disk for anything other than configuration files anyway (and all that worked fine before adding the datagrids).

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

My next thought was to try to step through some of the potential 'interesting' parts of the datagrid code and jump over any that were questionable. I did try that with the datagrid's ArrangeOverride, but the exception didn't seem to care whether I did that or not. Also, I am not sure this is a useful strategy. Since the exception isn't being thrown until after a cycle on the message loop, and since I can't know for sure when it's about to happen, I would need to cover a huge number of permutations, running each permutation a whole lot of times, to isolate the problem code.

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

The error is thrown in both Debug and Release modes. And, as a final background note, the amount of data we're dealing with here is small, much smaller than my 10000-row runs of the datagrid in isolation. It's probably on the order of 50-100 rows, with perhaps 30-40 columns. And before the exception is thrown, the data and the grids seem to work and respond fine.

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

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

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

Many thanks in advance for any help you can provide!

推荐答案

好的,还有一些 来自 Tim Heuer [MSFT] 的重要意见,我弄清楚发生了什么以及如何解决这个问题.

OK, with some critical input from Tim Heuer [MSFT], I figured out what was going on and how to get around this problem.

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

Surprisingly, none of my three initial guesses were correct. This wasn't about memory, threading, or system resources. Instead, it was about limitations in the Windows messaging system. Apparently it is a little like a stack overflow exception, in that when you make too many changes to the visual tree all at once, the asynchronous update queue gets so long that it trips a wire and the exception gets thrown.

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

In this case, the problem is that there are enough UIElements going into the data grid I am working with that allowing the grid to generate all its own columns all at once can in some cases exceed the limit. I was using a number of grids all at once, and all loading in response to page navigation events, which made it all the trickier to nail down.

幸运的是,我遇到的限制不是可视化树或 XAML UI 子系统本身的限制,只是用于更新它的消息传递中的限制.这意味着,如果我可以将相同的操作分散到调度程序时钟的多个滴答上,我就可以完成相同的最终结果.

Thankfully, the limitations I was running into were NOT limitations in the visual tree or the XAML UI subsystem itself, just in the messaging used to update it. This means that if I could spread out the same operations over multiple ticks of the dispatcher's clock, I could accomplish the same end result.

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

What I ended up doing was to instruct my data grid not to autogenerate its own columns. Instead, I embedded the grid into a user control that, when the data was loaded, would parse out the columns needed and load them into a list. Then, I called the following method:

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 是一种简单的类型,用于存放解析出的配置或从文件加载的配置.)

(ColumnDisplaySetup is a trivial type used to house the parsed-out configuration or a configuration loaded from a file.)

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

This method is called with the following arguments, respectively: list of columns, 0, and my arbitrary guess of 5 as a fairly safe number of columns to load at a time; but this number is based on testing and the expectation that a good number of grids could be loading simultaneously. I asked Tim for more information that might inform this part of the process, and will report back here if I learn more about how to determine how much is safe.

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

In practice, this seems to work adequately, although it results in the sort of progressive rendering you'd expect, with the columns visibly popping in. I expect this could be improved both by using the maximum possible value for numToLoad and by other UI sleight-of-hand. I may investigate hiding the grid while the columns are generated and only showing the result when everything is ready. Ultimately the decision will come down to which feels more 'fast and fluid'.

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

Again, I will update this answer with more information if I get it, but I hope this helps someone facing similar problems in the future. After pouring more time than I'd care to admit into the bug hunt, I don't want anyone else to have to kill themselves over this.

这篇关于神秘的“没有足够的配额可用于处理此命令"在 DataGrid 的 WinRT 端口中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆