web-dev-qa-db-ja.com

DataGridのWinRTポートで、神秘的な「このコマンドを処理するのに十分なクォータが利用できません」

9月26日編集

完全な背景については以下を参照してください。 tl; dr:データグリッドコントロールが奇妙な例外を引き起こしています。原因を特定して解決策を見つけるためのヘルプを探しています。

これをさらに絞り込みました。不安定な動作をより確実にトリガーすることで、より小さなテストアプリで動作を再現することができました。

私は間違いなくスレッドとメモリの問題の両方を除外することができます。新しいアプリはタスクやその他のスレッド/非同期機能を使用せず、DataGridに表示されるオブジェクトのクラスに定数を返すプロパティを追加するだけで、未処理の例外をトリガーできます。これは、問題が管理されていないリソースの枯渇か、まだ考えていないことのいずれかであることを示しています。

改訂されたプログラムはこのように構成されています。ラベルとデータグリッドを持つEntityCollectionGridViewというユーザーコントロールを作成しました。コントロールのLoadedイベントハンドラーで、List<TestClass>を1000または10000行のデータグリッドに追加し、グリッドに列を生成させます。このユーザーコントロールは、ページのOnNavigatedToイベント(またはLoaded、重要ではないようです)のMainPage.xamlで2〜4回インスタンス化されます。例外が発生した場合、MainPageが表示された直後に発生します。

興味深いのは、表示される行の数によって振る舞いが変化しないように見えることです(各グリッドで10000行で確実に動作するか、1000行で確実に失敗します)。むしろ、すべてのグリッドの列の総数指定された時間にロードされます。表示するプロパティが20個あるため、4つのグリッドが正常に機能します。 35のプロパティと4つのグリッドで、例外がスローされます。しかし、2つのグリッドを削除すると、35のプロパティを持つ同じクラスが正常に機能します。

20列から35列にジャンプするためにTestClassに追加するプロパティはすべて次の形式であることに注意してください。

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

そのため、バッキングデータに追加のメモリはありません(そして、やはり、メモリのプレッシャーが問題であるとは思いません)。

皆さんはどう思いますか?繰り返しになりますが、タスクマネージャーのハンドル/ユーザーオブジェクト/などは見た目が良いですが、他に何か足りないものはありますか

元の投稿

私は、Silverlight Toolkit DataGridのWinRTへの移植に取り組んできましたが、単純なテスト(さまざまな構成と10000行まで)で十分に機能しました。ただし、別のWinRTアプリに埋め込むことを試みたため、デバッグが非常に困難である散発的な例外(App.UnhandledExceptionハンドラーで発生するSystem.Exception型)に遭遇しました。

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

典型的なテストシナリオは、ルートから開始し、子に移動し、孫に移動し、子に戻り、再び孫に移動しようとすると、上記の例外で失敗します。しかし、孫に最初に当たったときに失敗するか、失敗する前に数回前後に移動する可能性があります。

呼び出しスタックには、マネージドフレームが1つしかありません。これは、未処理の例外イベントハンドラーです。これは非常に役に立ちません。混合モードデバッグに切り替えると、次のメッセージが表示されます。

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    

これは、アプリのメッセージループで少なくとも1サイクルが経過するまで、私が間違っていることは何も登録しないことを示しています(また、「デバッグ|例外...」私は言うことができます、何も投げられ、飲み込まれません)。興味深いスタックフレームは、WindowProcOnReentrancyProtectedWindowMessage、および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

...しかし、それは私にとっても何の意味もありません(関連性さえないかもしれません)。

私が思いつく3つの理論は次のとおりです。

  1. メモリのプレッシャー。しかし、物理メモリの24%が解放され、アプリが100MB未満のメモリを消費して、これに遭遇しました。その他の場合、アプリはしばらくナビゲートして、400MBのメモリを消費する問題にぶつかることはありません
  2. スレッドの問題、ワーカースレッドからUIスレッドへのアクセスなど。実際、バックグラウンドスレッドでデータアクセスが発生しています。しかし、これはWinForms環境とOutlookプラグインで非常に信頼性の高い(移植された)フレームワークを使用しているため、スレッドの使用は安全だと思います。さらに、ListViewsなどにバインドするだけで問題なくこのアプリで同じデータを使用できます。最後に、Grandchildノードは、最初のデータグリッドで行を選択すると、2番目のデータグリッドに表示される行の詳細項目の要求を開始するように構成されます(最初は空であり、例外を防ぐことなくそのままにすることができます)。これはページ遷移なしで発生し、選択をいじることを選択する限り問題なく動作します。しかし、Childに戻ると、その時点でデータアクセスがなく、したがって、私が知っているスレッド化操作はないはずですが、すぐに私を殺すかもしれません。
  3. 何らかのリソースの枯渇、おそらくGUIハンドル。しかし、私はこのシステムにそれほど大きな圧力をかけているとは思わない。 1回の実行で、例外ハンドラーを破壊すると、タスクマネージャーは、662のハンドル、21のユーザーオブジェクト、および12 GDIオブジェクトを使用して、734、37、および19を使用するTweetroと比較して、このカテゴリでは他に何が欠けているのでしょうか?

十分な空きディスク容量があり、設定ファイル以外にはディスクを使用していません(データグリッドを追加する前に問題なく機能していました)。

私の次の考えは、データグリッドコードの潜在的な「興味深い」部分のいくつかをステップスルーして、疑わしい部分を飛び越えようとすることでした。私はデータグリッドのArrangeOverrideでそれを試しましたが、例外は私がそれをしたかどうかを気にしませんでした。また、これが有用な戦略であるかどうかはわかりません。例外はメッセージループのサイクルが終わるまでスローされないため、またいつ発生するかわからないため、各置換を実行する膨大な数の置換をカバーする必要があります。問題コードを分離するために。

エラーは、デバッグモードとリリースモードの両方でスローされます。最後に、最後の注意事項として、ここで扱っているデータの量は小さく、単独でのデータグリッドの10000行の実行よりもはるかに小さくなっています。おそらく50〜100行程度で、おそらく30〜40列です。そして、例外がスローされる前に、データとグリッドは機能し、正常に応答しているようです。

だから、私はあなたに来るのです。私の2つの質問は:

  1. エラー情報から、問題の可能性に関するヒントが得られますか?
  2. 問題コードを分離するためにどのデバッグ戦略を使用しますか?

あなたが提供できる助けを事前に感謝します!

42
Dominic P

OK、いくつかの Tim Heuer [MSFT]からの重要な入力 で、私は何が起こっていたのか、この問題を回避する方法を見つけました。

驚くべきことに、私の最初の3つの推測はいずれも正しくありませんでした。これは、メモリ、スレッド、またはシステムリソースに関するものではありません。代わりに、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の手品による。列の生成中にグリッドを非表示にして、すべての準備ができたときにのみ結果を表示することを検討する場合があります。最終的には、決定は「高速で流動的」に感じられます。

繰り返しになりますが、この回答を入手できた場合はより多くの情報で更新しますが、これが将来同様の問題に直面している人の助けになることを願っています。バグハントを認めようと思っているよりも多くの時間を注ぎ込んだ後、私は他の誰かがこれについて自分自身を殺す必要がないようにしたい。

50
Dominic P

この問題は、Windows 8.1用にアプリケーションを再ターゲットした後、Windows 8.1 Previewで修正されたようです。数千のビジュアルを画面にダンプすることで、この問題を再現できなくなりました。

0
Gary Johnson