6

次のコード スニペットは、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

'***** コンソール アプリケーションは終了します。

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

コードはもともと別のスレッドとクラスにありましたが、これに単純化されました。

どんな助けでも大歓迎です。

ライアン

4

5 に答える 5

7

さて、私はそれを見つけました。これはフレームワークのバグであり、これを回避するには、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
于 2008-10-20T17:03:08.053 に答える
5

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

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

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

確かに、MS による小さな見落としのように感じます。

怠け者やなじみのない人にとっては、次のコードが機能します。

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 は文句を言うでしょうが、次のフレームワーク バージョンで修正されるかもしれません。リフレクションを使用したくない場合は、作成者が投稿したコードの方が「安全」と思われます。

チッ!

于 2010-03-09T16:23:39.477 に答える
0

面白い。この問題は、.net Framework 4.0 にも存在します。私のコードは猛烈にリークしていました。

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

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

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 ルーチン (わかりやすくするために省略されています) 内にあり、以前に投稿されたコードとほとんど同じです。

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

于 2011-08-18T05:39:30.567 に答える
0

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

于 2010-12-08T02:59:59.520 に答える
0

正式なアドバイスはできませんが、いくつかの考えがありました。

  • ループ内でメモリを監視したい場合は、ループ内でもメモリを収集する必要があります。そうしないと、設計上メモリ リークが発生するように見えます。これは、常に少量を収集するよりも、(必要に応じて) より少ない頻度で大きなブロックを収集する方が効率的であるためです。この場合、using ステートメントを作成するスコープ ブロック十分ですが、GC.Collect を使用すると、別のことが起こっている可能性があります。
  • GC.Collect でさえ提案にすぎません (非常に強力な提案ですが、それでも提案です)。すべての未処理のメモリが収集されることを保証するものではありません。
  • 内部 XPS コードが実際にメモリ リークを起こしている場合、OS にメモリ リークを強制的に収集させる唯一の方法は、アプリケーションが終了したと OS に認識させることです。そのためには、xps コードを処理し、メイン アプリから呼び出されるダミー アプリケーションを作成するか、xps コードをメイン コード内の独自の AppDomain に移動するだけで十分な場合があります。
于 2008-10-20T15:14:19.583 に答える