質問

次のコードスニペットは、XPSファイルを開くときのメモリリークを示しています。実行してタスクマネージャーを見ると、アプリが終了するまで成長し、メモリを解放しません。

'******コンソールアプリケーションの開始。

Module Main

    Const DefaultTestFilePath As String = "D:\Test.xps"
    Const DefaultLoopRuns As Integer = 1000

    Public Sub Main(ByVal Args As String())
        Dim PathToTestXps As String = DefaultTestFilePath
        Dim NumberOfLoops As Integer = DefaultLoopRuns

        If (Args.Count >= 1) Then PathToTestXps = Args(0)
        If (Args.Count >= 2) Then NumberOfLoops = CInt(Args(1))

        Console.Clear()
        Console.WriteLine("Start - {0}", GC.GetTotalMemory(True))
        For LoopCount As Integer = 1 To NumberOfLoops

            Console.CursorLeft = 0
            Console.Write("Loop {0:d5}", LoopCount)

            ' The more complex the XPS document and the more loops, the more memory is lost.
            Using XPSItem As New Windows.Xps.Packaging.XpsDocument(PathToTestXps, System.IO.FileAccess.Read)
                Dim FixedDocSequence As Windows.Documents.FixedDocumentSequence

                ' This line leaks a chunk of memory each time, when commented out it does not.
                FixedDocSequence = XPSItem.GetFixedDocumentSequence
            End Using
        Next
        Console.WriteLine()
        GC.Collect() ' This line has no effect, I think the memory that has leaked is unmanaged (C++ XPS internals).
        Console.WriteLine("Complete - {0}", GC.GetTotalMemory(True))

        Console.WriteLine("Loop complete but memory not released, will release when app exits (press a key to exit).")
        Console.ReadKey()

    End Sub

End Module

'******コンソールアプリケーションENDS。

1000回ループする理由は、コードが大量のファイルを処理し、メモリをすばやくリークしてOutOfMemoryExceptionを強制するためです。ガベージコレクションの強制は機能しません(XPS内部の管理されていないメモリの塊であると思われます)。

コードはもともと別のスレッドとクラスにありましたが、これに簡略化されています。

ご協力ありがとうございます。

ライアン

役に立ちましたか?

解決

まあ、見つけました。これはフレームワークのバグであり、回避するにはUpdateLayoutの呼び出しを追加します。 usingステートメントは、修正するために次のように変更できます。

        Using XPSItem As New Windows.Xps.Packaging.XpsDocument(PathToTestXps, System.IO.FileAccess.Read)
            Dim FixedDocSequence As Windows.Documents.FixedDocumentSequence
            Dim DocPager As Windows.Documents.DocumentPaginator

            FixedDocSequence = XPSItem.GetFixedDocumentSequence
            DocPager = FixedDocSequence.DocumentPaginator
            DocPager.ComputePageCount()

            ' This is the fix, each page must be laid out otherwise resources are never released.'
            For PageIndex As Integer = 0 To DocPager.PageCount - 1
                DirectCast(DocPager.GetPage(PageIndex).Visual, Windows.Documents.FixedPage).UpdateLayout()
            Next
            FixedDocSequence = Nothing
        End Using

他のヒント

今日はこれを試してみてください。興味深いことに、Reflector.NETを使用して物事をじっと見たときに、現在のDispatcherに関連付けられているContextLayoutManagerでUpdateLayout()を呼び出すことに関する修正が見つかりました。 (読む:ページを繰り返す必要はありません。)

基本的に、呼び出されるコード(ここではリフレクションを使用)は次のとおりです。

ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();

間違いなく、MSによる小さな監視のように感じます。

怠zyな、またはなじみのない人のために、このコードは機能します:

Assembly presentationCoreAssembly = Assembly.GetAssembly(typeof (System.Windows.UIElement));
Type contextLayoutManagerType = presentationCoreAssembly.GetType("System.Windows.ContextLayoutManager");
object contextLayoutManager = contextLayoutManagerType.InvokeMember("From",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new[] {dispatcher});
contextLayoutManagerType.InvokeMember("UpdateLayout", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, contextLayoutManager, null);

FxCopは文句を言いますが、おそらく次のフレームワークバージョンで修正されるでしょう。著者が投稿したコードは「安全」だと思われます。リフレクションを使用したくない場合。

HTH!

信頼できるアドバイスをすることはできませんが、いくつかの考えがありました:

  • ループ内のメモリを監視する場合は、ループ内のメモリも収集する必要があります。そうしないと、常に少量を収集するよりも(必要に応じて)より大きなブロックをより頻繁に収集する方が効率的であるため、設計によりメモリリークが発生します 。この場合、usingステートメントを作成するスコープブロックで十分です が必要ですが、GC.Collectを使用すると、何か他のことが行われている可能性があります。
  • GC.Collectでさえ提案です(大丈夫、非常に強い提案ですが、まだ提案です):未処理のメモリがすべて収集されることを保証するものではありません。
  • 内部XPSコードが本当にメモリをリークしている場合、OSに強制的に収集させる唯一の方法は、OSをだましてアプリケーションが終了したと判断させることです。そのためには、xpsコードを処理してメインアプリから呼び出されるダミーアプリケーションを作成するか、xpsコードをメインコード内の独自のAppDomainに移動するだけで十分かもしれません。

UpdateLayoutを追加しても問題を解決できません。 http://support.microsoft.com/kb/942443 によると、" PresentationCoreをプリロードするプライマリアプリケーションドメインの.dllファイルまたはPresentationFramework.dllファイル"が必要です。

興味深い。問題は.net framework 4.0にまだ存在します。私のコードは猛烈に漏れていました。

修正案-FixedDocumentSequenceの作成直後にUpdateLayoutがループで呼び出される場合、400ページのテストドキュメントでの問題は修正されませんでした。

ただし、次のソリューションDIDが問題を解決します。以前の修正と同様に、For-each-pageループの外側でGetFixedDocumentSequence()の呼び出しを移動しました。 「使用」条項...それが正しいかまだわからないという公正な警告。しかし、それは痛くない。その後、ドキュメントは画面上でページプレビューを生成するために再利用されます。痛くないようです。

DocumentPaginator paginator 
     =  document.GetFixedDocumentSequence().DocumentPaginator;
int numberOfPages = paginator.ComputePageCount();


for (int i = 0; i < NumberOfPages; ++i)
{
    DocumentPage docPage = paginator.GetPage(nPage);
    using (docPage)   // using is *probably* correct.
    {
        //  VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV

        ((FixedPage)(docPage.Visual)).UpdateLayout();

        //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //  Adding THAT line cured my leak.

        RenderTargetBitmap bitmap = GetXpsPageAsBitmap(docPage, dpi);

        .... etc...
    }

}

実際には、修正行はGetXpsPageAsBitmapルーチン(わかりやすくするため省略されています)内にあります。これは以前に投稿されたコードとほとんど同じです。

貢献してくれたすべての人に感謝します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top