سؤال

يوضح مقتطف التعليمات البرمجية التالي حدوث تسرب للذاكرة عند فتح ملفات 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

'****** ينتهي تطبيق وحدة التحكم.

السبب وراء تكراره ألف مرة هو أن الكود الخاص بي يعالج الكثير من الملفات ويتسرب من الذاكرة بسرعة مما يؤدي إلى حدوث OutOfMemoryException.فرض تجميع البيانات المهملة لا يعمل (أظن أنه جزء من الذاكرة غير مُدار في أجهزة XPS الداخلية).

كان الكود في الأصل في موضوع وفئة أخرى ولكن تم تبسيطه لهذا الغرض.

أي مساعدة موضع تقدير كبير.

ريان

هل كانت مفيدة؟

المحلول

حسنا، لقد وجدت ذلك. ذلك هو الشوائب في إطار وكمحاولة للتغلب على ذلك عليك إضافة استدعاء UpdateLayout. عن طريق بيان يمكن تغييرها لما يلي لتوفير الإصلاح؛

        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، وجدت الإصلاح تشارك يدعو UpdateLayout () على ContextLayoutManager المرتبطة مرسل الحالي. (اقرأ: لا حاجة لتكرار عبر صفحات).

والأساس، ورمز ليتم استدعاؤها (استخدام انعكاس هنا) هي:

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 سوف يشكو، ولكن ربما انها ثابتة في النسخة إطار المقبل. رمز نشرت من قبل المؤلف ويبدو أن "أكثر أمنا" إذا كنت تفضل عدم استخدام التفكير.

وHTH!

لا أستطيع أن أقدم لك أي نصيحة موثوقة، ولكن لدي بعض الأفكار:

  • إذا كنت تريد مشاهدة ذاكرتك داخل الحلقة، فأنت بحاجة إلى جمع الذاكرة داخل الحلقة أيضًا.وإلا فسوف يظهر لتسريب الذاكرة حسب التصميم، نظرًا لأنه من الأكثر كفاءة تجميع كتل أكبر بشكل أقل تكرارًا (حسب الحاجة) بدلاً من تجميع كميات صغيرة باستمرار.في هذه الحالة، تقوم كتلة النطاق بإنشاء عبارة الاستخدام يجب يكون ذلك كافيًا، ولكن استخدامك لـ GC.Collect يشير إلى احتمال حدوث شيء آخر.
  • حتى GC.Collect هو مجرد اقتراح (حسنًا، جدًا قوي اقتراح، ولكن لا يزال اقتراح):لا يضمن أن يتم جمع كل الذاكرة المعلقة.
  • إذا كان كود XPS الداخلي يتسرب من الذاكرة بالفعل، فإن الطريقة الوحيدة لإجبار نظام التشغيل على جمعها هي خداع نظام التشغيل للاعتقاد بأن التطبيق قد انتهى.للقيام بذلك، ربما يمكنك إنشاء تطبيق وهمي يتعامل مع كود xps الخاص بك ويتم استدعاؤه من التطبيق الرئيسي، أو قد يكون نقل كود xps إلى AppDomain الخاص به داخل الكود الرئيسي كافيًا أيضًا.

إضافة UpdateLayout لا يمكن أن تحل هذه القضية. ووفقا ل http://support.microsoft.com/kb/942443 و "تهيئة تحميلها على PresentationCore. هناك حاجة إلى ملف dll أو ملف PresentationFramework.dll في مجال التطبيق الأساسي ".

ومثيرة للاهتمام. المشكلة لا تزال موجودة في الإطار الصافي 4.0. قانون بلدي كان تسرب بشراسة.

والإصلاح المقترحة - حيث يسمى UpdateLayout في حلقة مباشرة بعد إنشاء FixedDocumentSequence لم يحل مشكلة بالنسبة لي على وثيقة اختبار 400 صفحة.

ومع ذلك، فإن الحل التالي أتم إصلاح مشكلة بالنسبة لي. كما هو الحال في الإصلاحات السابقة، وانتقلت الدعوة إلى 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 روتين (ommited لوضوح)، وهي جميلة متطابقة كثيرا إلى رمز سبق نشرها.

وشكرا لجميع من ساهم.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top