web-dev-qa-db-fra.com

Mystérieux "Pas assez de quota disponible pour traiter cette commande" dans le port WinRT de DataGrid

Modifier le 26 septembre

Voir ci-dessous pour l'arrière-plan complet. tl; dr: Un contrôle de grille de données provoque des exceptions étranges, et je cherche de l'aide pour isoler la cause et trouver une solution.

J'ai réduit cela un peu plus loin. J'ai pu reproduire le comportement dans une application de test plus petite avec un déclenchement plus fiable du comportement erratique.

Je peux définitivement exclure les problèmes de thread et (je pense) de mémoire. La nouvelle application n'utilise pas de tâches ou d'autres fonctionnalités de thread/asynchrone, et je peux déclencher l'exception non gérée simplement en ajoutant des propriétés qui renvoient une constante à la classe d'objets affichée dans le DataGrid. Cela m'indique que le problème est soit l'épuisement des ressources non gérées, soit quelque chose auquel je n'ai pas encore pensé.

Le programme révisé est structuré comme ceci. J'ai créé un contrôle utilisateur appelé EntityCollectionGridView qui a une étiquette et une grille de données. Dans le gestionnaire d'événements Loaded du contrôle, j'affecte un List<TestClass> à la grille de données avec 1000 ou 10000 lignes, permettant à la grille de générer les colonnes. Ce contrôle utilisateur est instancié 2 à 4 fois dans MainPage.xaml dans l'événement OnNavigatedTo de la page (ou Loaded, cela ne semble pas avoir d'importance). Si une exception se produit, elle se produit immédiatement après l'affichage de MainPage.

La chose intéressante est que le comportement ne semble pas varier avec le nombre de lignes affichées (cela fonctionnera de manière fiable avec 10000 lignes ou échouera de manière fiable avec seulement 1000 lignes dans chaque grille) mais plutôt avec le nombre total de colonnes dans toutes les grilles chargé à un moment donné. Avec 20 propriétés à afficher, 4 grilles fonctionnent bien. Avec 35 propriétés et 4 grilles, l'exception est levée. Mais si j'élimine deux grilles, la même classe avec 35 propriétés fonctionnera bien.

Notez que toutes les propriétés que j'ajoute à TestClass pour passer de 20 à 35 colonnes sont de la forme:

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

Il n'y a donc pas de mémoire supplémentaire dans les données de sauvegarde (et encore une fois, je ne pense pas que la pression de la mémoire soit le problème de toute façon).

Qu'en pensez-vous tous? Encore une fois, les poignées/objets utilisateur/etc dans le Gestionnaire des tâches semblent bien, mais y a-t-il autre chose qui pourrait me manquer?

Message d'origine

J'ai travaillé sur un port du Silverlight Toolkit DataGrid vers WinRT, et il a assez bien fonctionné dans des tests simples (une variété de configurations et jusqu'à 10000 lignes). Cependant, comme j'ai essayé de l'intégrer dans une autre application WinRT, j'ai rencontré une exception sporadique (de type System.Exception, déclenchée dans le gestionnaire App.UnhandledException) qui s'avère très difficile à déboguer.

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

L'erreur est toujours reproductible, mais pas de manière déterministe. Autrement dit, je peux y arriver à chaque fois que je lance l'application, mais cela ne se produit pas toujours en effectuant le même ensemble d'étapes exactement le même nombre de fois. L'erreur semble se produire lors des transitions de page (que vous naviguiez vers une nouvelle page vers l'avant ou vers une page précédente), et non (par exemple) lorsque vous modifiez la ItemsSource de la grille de données.

La structure de l'application est essentiellement un accès récursif via une hiérarchie, avec une page affichée à chaque niveau de la hiérarchie. Sur la page du nœud actuel de la hiérarchie, chaque nœud enfant et certains nœuds petits-enfants sont affichés, et pour tout sous-nœud, une grille de données peut être affichée. Dans la pratique, je reproduis systématiquement cela avec la structure de navigation suivante:

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

Un scénario de test typique est de commencer à la racine, de passer à l'enfant, de passer à petit-enfant, de revenir à enfant, puis lorsque j'essaie de naviguer à nouveau vers petit-enfant, il échoue à l'exception que j'ai mentionnée ci-dessus. Mais cela pourrait échouer la première fois que je frapperais Petit-enfant, ou cela pourrait me laisser faire plusieurs allers-retours avant d'échouer.

La pile d'appels ne comporte qu'une seule trame gérée, qui est le gestionnaire d'événements d'exception non géré. C'est très inutile. En passant au débogage en mode mixte, j'obtiens ce qui suit:

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    

Cela m'indique que tout ce que je fais mal ne s'enregistre qu'après au moins un cycle dans la boucle de message de l'application (et j'ai également essayé de casser toutes les exceptions levées en utilisant "Debug | Exceptions ..." - pour autant que Je peux dire, rien n'est jeté et avalé). Les cadres de pile intéressants que je vois sont WindowProc, OnReentrancyProtectedWindowMessage et Tick. Le msg est 0x402 (1026), ce qui ne signifie rien pour moi. Cette page répertorie ce message tel qu'il est utilisé dans les contextes suivants:

CBEM_SETIMAGELIST 
DDM_CLOSE 
DM_REPOSITION 
HKM_GETHOTKEY 
PBM_SETPOS 
RB_DELETEBAND 
SB_GETTEXTA 
TB_CHECKBUTTON 
TBM_GETRANGEMAX 
WM_PSD_MINMARGINRECT

... mais cela ne signifie pas grand-chose pour moi non plus (cela pourrait même ne pas être pertinent).

Les trois théories que je peux proposer sont les suivantes:

  1. Pression de la mémoire. Mais j'ai rencontré cela avec 24% de ma mémoire physique libre et l'application consommant moins de 100 Mo de mémoire. D'autres fois, l'application n'aura pas rencontré de problèmes de navigation pendant un certain temps et d'accumuler 400 Mo de mémoire
  2. Problèmes de thread, tels que l'accès au thread d'interface utilisateur à partir d'un thread de travail. Et, en fait, j'ai accès aux données sur un thread d'arrière-plan. Mais cela utilise un framework (porté) qui a été très fiable dans un environnement WinForms et dans un plugin Outlook, et je pense que l'utilisation des threads est sûre. De plus, je peux utiliser les mêmes données dans cette application sans aucun problème de liaison uniquement à ListViews et ainsi de suite. Enfin, le nœud Petit-enfant est configuré de telle sorte que la sélection d'une ligne dans la première grille de données lance une demande pour les éléments de détail de la ligne, qui sont affichés dans la deuxième grille de données (qui est initialement vide et peut le rester sans empêcher l'exception). Cela se produit sans transition de page et fonctionne parfaitement tant que je choisis de jouer avec la sélection. Mais revenir à Child pourrait me tuer tout de suite, même s'il ne devrait pas y avoir d'accès aux données à ce stade et donc pas d'opérations de threading que je sache.
  3. Épuisement des ressources d'une certaine sorte, peut-être des poignées GUI. Mais je ne pense pas que je mets autant de pression sur ce système. Dans une exécution, interrompant le gestionnaire d'exceptions, le Gestionnaire des tâches signale le processus en utilisant 662 poignées, 21 objets Utilisateur et 12 GDI objets, par rapport à Tweetro qui utilise 734, 37 et 19 respectivement sans problèmes. Que puis-je manquer de plus dans cette catégorie?

J'ai beaucoup d'espace disque libre et je n'utilise de toute façon pas le disque pour autre chose que des fichiers de configuration (et tout cela a bien fonctionné avant d'ajouter les datagrids).

Ma prochaine pensée a été d'essayer de parcourir certaines des parties "intéressantes" potentielles du code de la grille de données et de sauter celles qui étaient douteuses. J'ai essayé cela avec l'ArrangeOverride de la grille de données, mais l'exception ne semblait pas se soucier de savoir si je l'avais fait ou non. De plus, je ne suis pas sûr que ce soit une stratégie utile. Étant donné que l'exception n'est levée qu'après un cycle sur la boucle de message et que je ne peux pas savoir avec certitude quand elle est sur le point de se produire, je devrais couvrir un grand nombre de permutations, en exécutant chaque permutation beaucoup de fois, pour isoler le code du problème.

L'erreur est renvoyée dans les modes Débogage et Sortie. Et, comme note de fond finale, la quantité de données dont nous traitons ici est petite, beaucoup plus petite que mes 10000 lignes de la grille de données isolément. C'est probablement de l'ordre de 50 à 100 lignes, avec peut-être 30 à 40 colonnes. Et avant que l'exception ne soit levée, les données et les grilles semblent fonctionner et répondre correctement.

Donc, c'est pourquoi je viens à vous. Mes deux questions sont:

  1. Les informations sur l'erreur vous donnent-elles des indications sur le problème potentiel?
  2. Quelle stratégie de débogage utiliseriez-vous pour isoler le code du problème?

Merci d'avance pour toute aide que vous pouvez fournir!

42
Dominic P

OK, avec quelques contribution critique de Tim Heuer [MSFT] , j'ai compris ce qui se passait et comment contourner ce problème.

Étonnamment, aucune de mes trois suppositions initiales n'était correcte. Il ne s'agissait pas de mémoire, de thread ou de ressources système. Au lieu de cela, il s'agissait de limitations dans le système de messagerie Windows. Apparemment, cela ressemble un peu à une exception de dépassement de pile, en ce sens que lorsque vous apportez trop de modifications à l'arborescence visuelle en même temps, la file d'attente de mise à jour asynchrone devient si longue qu'elle déclenche un câble et que l'exception est levée.

Dans ce cas, le problème est qu'il y a suffisamment d'éléments UIE dans la grille de données avec laquelle je travaille, ce qui permet à la grille de générer toutes ses propres colonnes à la fois peut, dans certains cas, dépasser la limite. J'utilisais un certain nombre de grilles à la fois, et tout le chargement en réponse aux événements de navigation de page, ce qui rendait la tâche plus difficile à clouer.

Heureusement, les limitations que je rencontrais n'étaient PAS des limitations dans l'arborescence visuelle ou le sous-système d'interface utilisateur XAML lui-même, juste dans la messagerie utilisée pour le mettre à jour. Cela signifie que si je pouvais répartir les mêmes opérations sur plusieurs ticks de l'horloge du répartiteur, je pourrais obtenir le même résultat final.

J'ai fini par demander à ma grille de données de ne pas générer automatiquement ses propres colonnes. Au lieu de cela, j'ai incorporé la grille dans un contrôle utilisateur qui, lorsque les données étaient chargées, analysait les colonnes nécessaires et les chargeait dans une liste. Ensuite, j'ai appelé la méthode suivante:

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 est un type trivial utilisé pour héberger la configuration analysée ou une configuration chargée à partir d'un fichier.)

Cette méthode est appelée avec les arguments suivants, respectivement: liste des colonnes, 0, et ma supposition arbitraire de 5 comme un nombre assez sûr de colonnes à charger à la fois; mais ce nombre est basé sur des tests et sur l'hypothèse qu'un bon nombre de grilles pourraient se charger simultanément. J'ai demandé à Tim plus d'informations qui pourraient éclairer cette partie du processus, et je ferai rapport ici si j'en apprends plus sur la façon de déterminer la quantité sûre.

En pratique, cela semble fonctionner correctement, même si cela se traduit par le type de rendu progressif auquel vous vous attendez, avec les colonnes apparemment visibles. Je m'attends à ce que cela puisse être amélioré à la fois en utilisant la valeur maximale possible pour numToLoad et par d'autres tours de passe-passe de l'interface utilisateur. Je peux étudier la possibilité de masquer la grille pendant la génération des colonnes et d'afficher le résultat uniquement lorsque tout est prêt. En fin de compte, la décision reviendra à celle qui semble plus "rapide et fluide".

Encore une fois, je mettrai à jour cette réponse avec plus d'informations si je l'obtiens, mais j'espère que cela aidera quelqu'un confronté à des problèmes similaires à l'avenir. Après avoir consacré plus de temps que je ne voudrais bien l'admettre dans la chasse aux insectes, je ne veux pas que quelqu'un d'autre doive se suicider pour cela.

50
Dominic P

Il semble que ce problème soit résolu dans l'aperçu de Windows 8.1, après avoir reciblé mon application pour Windows 8.1. Je ne peux plus recréer ce problème en déversant des milliers de visuels sur l'écran.

0
Gary Johnson