2012-09-25 18 views
35

Editar Sept 26misterioso "No hay suficiente cuota está disponible para procesar este comando" en el puerto de la cuadrícula de datos WinRT

Ver abajo para el fondo completo. tl; dr: un control de cuadrícula de datos está causando excepciones impares, y estoy buscando ayuda para aislar la causa y encontrar una solución.

He reducido esto un poco más. Pude reproducir el comportamiento en una aplicación de prueba más pequeña con un desencadenamiento más confiable del comportamiento errático.

Definitivamente puedo descartar tanto el enhebrado como (creo) problemas de memoria. La nueva aplicación no utiliza tareas u otras funciones de subprocesamiento/asíncronas, y puedo desencadenar la excepción no controlada simplemente agregando propiedades que devuelven una constante a la clase de objetos que se muestra en el DataGrid. Esto me indica que el problema está en el agotamiento de recursos no administrados o en algo que aún no he pensado.

El programa revisado está estructurado de esta manera. Creé un control de usuario llamado EntityCollectionGridView que tiene una etiqueta y una cuadrícula de datos. En el controlador de eventos Loaded del control, asigno un List<TestClass> a la cuadrícula de datos con 1000 o 10000 filas, dejando que la grilla genere las columnas. Este control de usuario se crea una instancia 2-4 veces en MainPage.xaml en el evento OnNavigatedTo de la página (o Loaded, parece que no importa). Si se produce una excepción, se produce inmediatamente después de que se muestre MainPage.

Lo interesante es que el comportamiento no parece variar con la cantidad de filas mostradas (funcionará de manera confiable con 10000 filas o fallará de manera confiable con solo 1000 filas en cada cuadrícula) sino con el número total de columnas en todas las cuadrículas cargadas en un momento dado. Con 20 propiedades para mostrar, 4 cuadrículas funciona bien. Con 35 propiedades y 4 cuadrículas, se lanza la excepción. Pero si elimino dos cuadrículas, la misma clase con 35 propiedades funcionará bien.

Tenga en cuenta que todas las propiedades agrego a TestClass para saltar de 20 a 35 columnas son de la forma:

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

Por lo tanto, no hay memoria adicional en los datos de respaldo (y de nuevo, no me' t pensar que la presión de la memoria es el problema de todos modos).

¿Qué piensan todos? Nuevamente, los manejadores/objetos de usuario/etc en el Administrador de tareas se ven bien, pero ¿hay algo más que me pueda estar perdiendo?

Post original

He estado trabajando en un puerto del juego de herramientas DataGrid a WinRT, y lo ha hecho bastante bien en las pruebas simples (una variedad de configuraciones y hasta 10000 filas). Sin embargo, cuando intenté incorporarlo a otra aplicación WinRT, me encontré con una excepción esporádica (de tipo System.Exception, creada en el controlador App.UnhandledException) que está demostrando ser muy difícil de depurar.

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

El error es constantemente reproducible, pero no determinista. Es decir, puedo hacer que suceda cada vez que ejecuto la aplicación, pero no siempre ocurre al realizar el mismo conjunto exacto de pasos la misma cantidad de veces. El error parece ocurrir en las transiciones de página (ya sea al navegar hacia una página nueva hacia delante o hacia atrás a una página anterior), y no (por ejemplo) al cambiar la fuente de elementos de la cuadrícula de datos.

La estructura de la aplicación es básicamente acceso recursivo a través de una jerarquía, con una página que se muestra en cada nivel de jerarquía. En la página para el nodo actual en la jerarquía, se muestran todos los nodos secundarios y algunos nodos nietos, y para cualquier subnodo se puede mostrar una cuadrícula de datos. En la práctica, yo constantemente reproducir este con la siguiente estructura de navegación:

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 escenario típico de pruebas está, se inicia en la base y se mueve con el niño, mover a Nieto, volver a niño, y luego cuando intento navegar a Nieto nuevamente, falla con la excepción que mencioné anteriormente. Pero podría fallar la primera vez que golpee a Nieto, o podría permitirme avanzar y retroceder varias veces antes de fallar.

La pila de llamadas tiene solo un marco administrado, que es el controlador de eventos de excepción no controlada. Esto es muy inútil. El cambio a la depuración en modo mixto, me sale el siguiente:

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++ 
[email protected]() + 0x23 bytes 
[email protected]() + 0xbd bytes 
[email protected]() + 0xf8 bytes 
[email protected]() + 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++ 
[email protected]() + 0xceab bytes  
[email protected]@12() + 0xe bytes 
[email protected]() + 0x27 bytes 
[email protected]() + 0x1b bytes  

Esto me indica que todo lo que estoy haciendo mal no se registra hasta después de al menos un ciclo en el bucle de mensajes de la aplicación (y también intenté romper en todas las excepciones arrojadas que usan "Depurar | Excepciones ..." - hasta donde puedo ver, no se arroja ni se ingiere nada). Los interesantes marcos de pila que veo son WindowProc, OnReentrancyProtectedWindowMessage y Tick. El msg es 0x402 (1026), lo que no significa nada para mí. This page listas de mensajes tal como se utiliza en los siguientes contextos:

CBEM_SETIMAGELIST 
DDM_CLOSE 
DM_REPOSITION 
HKM_GETHOTKEY 
PBM_SETPOS 
RB_DELETEBAND 
SB_GETTEXTA 
TB_CHECKBUTTON 
TBM_GETRANGEMAX 
WM_PSD_MINMARGINRECT 

... pero eso no quiere decir nada mucho para mí, ya sea (que incluso podría no ser relevante).

Las tres teorías que se puede topar con estos son: la presión

  1. memoria. Pero me he topado con esto con el 24% de mi memoria física libre y la aplicación que consume menos de 100 MB de memoria. Otras veces, la aplicación no se han llegado a ningún problemas para desplazarse alrededor de un tiempo y acumulando 400 MB de memoria
  2. problemas de roscado, tales como el acceso al hilo de interfaz de usuario desde un subproceso de trabajo. Y, de hecho, tengo acceso a datos en un hilo de fondo. Pero esto está usando un marco (portado) que ha sido muy confiable en un entorno WinForms y en un plugin de Outlook, y creo que el uso del hilo es seguro. Además, puedo usar los mismos datos en esta aplicación sin ningún problema vinculando solo a ListViews y así sucesivamente. Finalmente, el nodo Nieto se configura de manera que al seleccionar una fila en la primera cuadrícula de datos se inicia una solicitud para los elementos de detalle de la fila, que se muestran en la segunda cuadrícula de datos (que está inicialmente vacía y puede permanecer así sin evitar la excepción). Esto sucede sin una transición de página y funciona sin problemas durante el tiempo que elija jugar con la selección. Pero volver a Child podría matarme de inmediato, aunque en ese momento no debería haber acceso a los datos y, por lo tanto, no debería enhebrar las operaciones que conozco.
  3. Agotamiento de recursos de algún tipo, tal vez controladores de GUI. Pero no creo que esté ejerciendo tanta presión sobre este sistema. En una ejecución, interrumpiendo el manejador de excepciones, el Administrador de tareas informa el proceso utilizando 662 identificadores, 21 objetos de usuario y 12 objetos GDI, en comparación con Tweetro que usa 734, 37 y 19, respectivamente, sin problemas. ¿Qué más podría extrañar en esta categoría?

Tengo un montón de espacio libre en disco, y no estoy utilizando el disco para nada más que archivos de configuración de todos modos (y todo eso funcionó bien antes de agregar las cuadrículas de datos).

Mi siguiente pensamiento fue tratar de pasar por algunas de las posibles partes "interesantes" del código de la cuadrícula de datos y saltar sobre cualquiera que fuera cuestionable. Lo intenté con ArrangeOverride de la cuadrícula de datos, pero a la excepción no pareció importarme si lo hice o no. Además, no estoy seguro de que esta sea una estrategia útil. Dado que la excepción no se lanza hasta después de un ciclo en el ciclo del mensaje, y como no puedo estar seguro de cuándo va a suceder, necesitaría cubrir una gran cantidad de permutaciones, ejecutando cada permutación un montón de veces, para aislar el código del problema.

El error se produce en ambos modos, Depurar y Liberar. Y, como nota de fondo final, la cantidad de datos con los que nos enfrentamos aquí es pequeña, mucho más pequeña que mis 10000 filas de la cuadrícula de datos de forma aislada. Probablemente sea del orden de 50-100 filas, con quizás 30-40 columnas. Y antes de lanzar la excepción, los datos y las cuadrículas parecen funcionar y responder bien.

Así que, es por eso que vengo a usted. Mis dos preguntas son:

  1. ¿La información del error le da alguna pista sobre cuál podría ser el problema?
  2. ¿Qué estrategia de depuración usaría para aislar el código del problema?

¡Muchas gracias de antemano por su ayuda!

Respuesta

42

OK, con algunos critical input from Tim Heuer [MSFT], descubrí qué estaba pasando y cómo solucionar este problema.

Sorprendentemente, ninguna de mis tres conjeturas iniciales fue correcta. No se trataba de memoria, enhebrado o recursos del sistema. En cambio, se trataba de limitaciones en el sistema de mensajería de Windows. Aparentemente es un poco como una excepción de desbordamiento de pila, ya que cuando realiza demasiados cambios en el árbol visual a la vez, la cola de actualización asíncrona se alarga tanto que se desconecta y se lanza la excepción.

En este caso, el problema es que hay suficientes elementos UIE entrando en la cuadrícula de datos con los que estoy trabajando que permiten que la grilla genere todas sus columnas al mismo tiempo, en algunos casos puede exceder el límite. Estaba usando varias cuadrículas al mismo tiempo, y todas cargadas en respuesta a los eventos de navegación de página, lo que hizo que todo fuera más complicado de definir.

Afortunadamente, las limitaciones con las que me encontraba no eran limitaciones en el árbol visual o en el subsistema XAML UI en sí, solo en los mensajes utilizados para actualizarlo. Esto significa que si pudiera extender las mismas operaciones en varios tics del reloj del despachador, podría lograr el mismo resultado final.

Lo que terminé haciendo fue ordenar a mi cuadrícula de datos que no autogenerara sus propias columnas. En cambio, incrustó la cuadrícula en un control de usuario que, cuando se cargaron los datos, analizaría las columnas necesarias y las cargaría en una lista. Entonces, que se llama el método siguiente:

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 es un tipo trivial utilizado para alojar la configuración analizada de salida o una configuración cargada desde un archivo.)

Se llama a este método con los siguientes argumentos, respectivamente: lista de columnas, 0 y mi estimación arbitraria de 5 como un número bastante seguro de columnas para cargar a la vez; pero este número se basa en pruebas y la expectativa de que un buen número de cuadrículas se puedan cargar simultáneamente. Le pedí a Tim más información que podría informar esta parte del proceso, e informaré aquí si aprendo más sobre cómo determinar cuánto es seguro.

En la práctica, esto parece funcionar adecuadamente, aunque da como resultado el tipo de interpretación progresiva que cabría esperar, con las columnas apareciendo visiblemente. Espero que esto pueda mejorarse utilizando el valor máximo posible para numToLoad y por otro truco de IU de mano. Puedo investigar cómo se oculta la cuadrícula mientras se generan las columnas y solo se muestra el resultado cuando todo está listo. En última instancia, la decisión será más rápida y fluida.

Nuevamente, actualizaré esta respuesta con más información si la recibo, pero espero que esto ayude a alguien a enfrentar problemas similares en el futuro. Después de dedicar más tiempo de lo que me gustaría admitir en la búsqueda de errores, no quiero que nadie más tenga que suicidarse por esto.

-1

Parece que este problema se corrigió en la Vista previa de Windows 8.1, después de redirigir mi aplicación para Windows 8.1. Ya no puedo volver a crear este problema volcando miles de imágenes en la pantalla.

+1

Parece que tampoco funciona en mi máquina Win8.1. – digitalMoto

+0

@digitalMoto ¿Quiere decir que ve esta falla en 8.1, o que no puede reproducir este problema en 8.1? –

+2

Esto parece poco probable. La documentación de PostMessage aquí: https://msdn.microsoft.com/en-us/library/ms644944.aspx?f=255&MSPPError=-2147217396 --- indica que "existe un límite de 10.000 mensajes publicados por cola de mensajes". y nada dice que hay un límite diferente en Windows 8.1 –

Cuestiones relacionadas