45

9月26日編集

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

これをさらに絞り込みました。私はより小さなテストアプリで動作を再現し、より信頼性の高い不規則な動作をトリガーすることができました.

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

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

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

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

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

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

コール スタックには、未処理の例外イベント ハンドラーであるマネージ フレームが 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 サイクル後まで登録されないことを示しています (また、「デバッグ | 例外...」を使用して、スローされたすべての例外を中断しようとしました。何も投げたり飲み込んだりしていないことがわかります)。私が見る興味深いスタック フレームは、、、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

...しかし、それは私にとってもあまり意味がありません (関係ないかもしれません)。

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

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

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

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

このエラーは、デバッグ モードとリリース モードの両方でスローされます。そして、最後のバックグラウンド ノートとして、ここで扱っているデータの量は少なく、データ グリッドを単独で実行した 10000 行よりもはるかに小さいです。おそらく 50 ~ 100 行程度で、おそらく 30 ~ 40 列です。例外がスローされる前は、データとグリッドは正常に動作しているように見えます。

だから、それが私があなたのところに来る理由です。私の2つの質問は次のとおりです。

  1. エラー情報は、何が問題であるかについてのヒントを与えてくれますか?
  2. 問題のコードを切り分けるために、どのデバッグ戦略を使用しますか?

あなたが提供できる助けを前もって感謝します!

4

2 に答える 2

57

わかりました。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。ただし、この数はテストと、かなりの数のグリッドが同時にロードされる可能性があるという予想に基づいています。プロセスのこの部分を知らせる可能性のある詳細情報をティムに尋ねました。どのくらいが安全かを判断する方法についてさらに学んだ場合は、ここで報告します.

実際には、これは適切に機能しているように見えますが、期待どおりのプログレッシブ レンダリングが行われ、列が目に見えて表示されます。これは、可能な最大値を使用することnumToLoadと他の UI の巧妙さの両方を使用することで改善できると期待しています。手の。列が生成されている間はグリッドを非表示にし、すべての準備が整ったときにのみ結果を表示することを検討することがあります。最終的には、どちらがより「速くて滑らか」に感じられるかが決定されます。

繰り返しますが、この回答を取得したら、より多くの情報で更新しますが、これが将来同様の問題に直面する人に役立つことを願っています. 認めたくないほど多くの時間をバグハントに注ぎ込んだ後、他の誰かがこれで自殺する必要がないようにしたいと思います.

于 2012-09-27T04:37:13.943 に答える