Ошибка памяти FlowDocument в C # - PullRequest
8 голосов
/ 05 июня 2009

В настоящее время я пытаюсь решить проблему с выпуском ресурсов FlowDocument. Я загружаю RTF-файл и помещаю его в FlowDocument с помощью TextRange.Load. Я заметил, что после этого он сохраняет эти ресурсы и GC не собирает их. Я запустил профилировщик памяти и увидел, что это правда. Я также сузил его до того места, где я загружаю, фактически помещаю rtf в FlowDocument. Если я этого не сделаю, тогда все в порядке. Итак, я знаю, что это проблема.

Я надеюсь, что у меня есть какие-то указания относительно того, как я могу решить эту проблему. Вот код, который загружает RTF и все. Я закомментировал весь другой код и даже поместил его в его собственную область, а также попробовал GC.Collect (). Любая помощь с благодарностью.

EDIT: Вот мой код в полном объеме на данный момент. Я взял все остальное, кроме самого необходимого, чтобы оно заработало. Проблема все еще там. Как вы можете видеть, FlowDocument и TextRange нигде не упоминаются.

    public LoadRTFWindow(string file)
    {
        InitializeComponent();

        using (FileStream reader = new FileStream(file, FileMode.Open))
        {
            FlowDocument doc = new FlowDocument();
            TextRange range = new TextRange(doc.ContentStart, doc.ContentEnd);
            range.Load(reader, System.Windows.DataFormats.Rtf);
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }

Я нашел этот пост , который, как я надеялся, поможет мне решить мою проблему, но мне не повезло с этим. Любая помощь очень ценится. Спасибо.

РЕДАКТИРОВАТЬ: Я думаю, я должен упомянуть основной способ, которым я проверяю это. У меня открыт диспетчер задач Windows, и я наблюдаю за использованием памяти процессом моего приложения. Когда я запускаю приведенный выше код, приложение переходит от 40000 К до 70000 К при выполнении TextRange.Load () (это большой 400-страничный RTF), и после его завершения оно падает до 61 000 К и остается там. Я ожидаю, что он снизится до 40000 К или, по крайней мере, очень близко к нему.

Как я уже упоминал ранее, я использовал профилировщик памяти и увидел, что там было много абзацев, Run..ect. объекты все еще живы впоследствии.

Ответы [ 8 ]

6 голосов
/ 23 июня 2009

Если я подтвердил, что произошла утечка памяти, вот что я бы сделал для устранения проблемы.

  1. Установите средства отладки для Windows с http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a
  2. Запустите Windbg из установочного каталога.
  3. Запустите ваше приложение и выполните операции, которые вызывают утечку памяти.
  4. Прикрепите Windbg к вашему приложению (F6).
  5. Тип .loadby sos mscorwks
  6. Тип !dumpheap -type FlowDocument
  7. Проверьте результат вышеприведенной команды. Если вы видите несколько FlowDocuments, для каждого значения первого столбца (который содержит адрес) выполните

Тип !gcroot <value of first column>

Это должно показать вам, кто держит ссылку.

3 голосов
/ 12 ноября 2010

У нас была похожая проблема, в которой мы создавали потоковый документ в другом потоке, я заметил в профилировщике памяти, что объекты все еще были там.

Насколько я знаю, как описано в этой ссылке

"Когда создается FlowDocument, для него также создаются относительно дорогие объекты контекста форматирования в его StructuralCache. Когда вы создаете несколько потоковых документов в тесном цикле, StructuralCache создается для каждого FlowDoc. Давайте вызовем Gc.Collect в конец цикла, в надежде восстановить часть памяти. StructuralCache имеет финализатор освобождает этот контекст форматирования, но не сразу. Финализатор эффективно планирует операцию для освобождения контекстов в DispatcherPriority.Background. "

Таким образом, до тех пор, пока диспетчерские операции не будут завершены, документ Flow будет находиться в памяти. Таким образом, идея состоит в том, чтобы завершить диспетчерские операции.

Если вы находитесь в потоке, в котором в данный момент работает Dispatcher, попробуйте код, приведенный ниже, он принудительно завершит все операции в очереди, поскольку SystemIdle имеет самый низкий приоритет:

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.SystemIdle, 
    new DispatcherOperationCallback(delegate { return null; }), null); 

Если вы находитесь в потоке, в котором Dispatcher не запущен, так как в моем случае в потоке был создан только один поток документа, поэтому я попробовал что-то вроде:

var dispatcher = Dispatcher.CurrentDispatcher;
dispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle);
Dispatcher.Run();

это поставит в очередь завершение работы в самом конце, а затем запустит диспетчер для очистки FlowDocument, а затем в конце остановит диспетчер.

1 голос
/ 24 июня 2009

Я ранее сделал до # 7. Это был первый раз, когда я использовал Windbg, и поэтому я не знал, что делать с адресом, чтобы найти ссылки. Вот что я получил.

 Address       MT     Size
0131c9c0 55cd21d8       84     
013479e0 55cd21d8       84     
044dabe0 55cd21d8       84     
total 3 objects
Statistics:
      MT    Count    TotalSize Class Name
55cd21d8        3          252 System.Windows.Documents.FlowDocument
Total 3 objects
0:011> !gcroot 0131c9c0
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 47c
Scan Thread 2 OSTHread be8
Scan Thread 4 OSTHread 498
DOMAIN(001657B0):HANDLE(WeakSh):911788:Root:0131ff98(System.EventHandler)->
0131fcd4(System.Windows.Documents.AdornerLayer)->
012fad68(MemoryTesting.Window2)->
0131c9c0(System.Windows.Documents.FlowDocument)
DOMAIN(001657B0):HANDLE(WeakSh):911cb0:Root:0131ca90(MS.Internal.PtsHost.PtsContext)->
0131cb14(MS.Internal.PtsHost.PtsContext+HandleIndex[])->
0133d668(MS.Internal.PtsHost.TextParagraph)->
0131c9c0(System.Windows.Documents.FlowDocument)
DOMAIN(001657B0):HANDLE(WeakSh):9124a8:Root:01320a2c(MS.Internal.PtsHost.FlowDocumentPage)->
0133d5d0(System.Windows.Documents.TextPointer)->
0131ca14(System.Windows.Documents.TextContainer)->
0131c9c0(System.Windows.Documents.FlowDocument)

(я поместил его в блок кода для удобства чтения). Это после того как я закрыл окно. Похоже, на него ссылаются несколько вещей. Теперь, когда я знаю это, как мне освободить эти ссылки, чтобы они могли выпустить FlowDocument.

Спасибо за вашу помощь. Я чувствую, что наконец-то набираю силу.

1 голос
/ 05 июня 2009

FlowDocument использует System.Windows.Threading.Dispatcher для освобождения всех ресурсов. Он не использует финализаторы, потому что финализаторы будут блокировать текущий поток, пока все ресурсы не будут свободны. Таким образом, пользователь может увидеть некоторые зависания пользовательского интерфейса и так далее. Диспетчеры работают в фоновом потоке и оказывают меньшее влияние на пользовательский интерфейс.
Так что вызывая GC.WaitForPendingFinalizers (); не имеет никакого влияния на это. Вам просто нужно добавить код для ожидания и позволить Диспетчерам завершить свою работу. Просто попробуйте добавить что-то вроде Thread.CurrentThread.Sleep (2000 / * 2 сек * /);

EDIT: Мне кажется, что вы нашли эту проблему во время отладки какого-либо приложения. Я написал следующий очень простой тестовый пример (консольная программа):

    static void Main(string[] args)
    {
        Console.WriteLine("press enter to start");
        Console.ReadLine();

        var path = "c:\\1.rtf";

        for (var i = 0; i < 20; i++)
        {
            using (var stream = new FileStream(path, FileMode.Open))
            {
                var document = new FlowDocument();
                var range = new TextRange(document.ContentStart, document.ContentEnd);

                range.Load(stream, DataFormats.Rtf);
            }
        }

        Console.WriteLine("press enter to run GC.");
        Console.ReadLine();

        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine("GC has finished .");
        Console.ReadLine();
    }

Я пытался воспроизвести проблему. Я запускал его несколько раз, и он работал отлично - не было никаких утечек (около 3,2Kb все время и 36 ручек). Я не мог воспроизвести его, пока не запустил эту программу в режиме отладки в VS (просто f5 вместо ctrl + f5). Я получил 20,5Kb в начале, 31,7Kb после загрузки и до GC и 31Kb после GC. Это похоже на ваши результаты.
Итак, не могли бы вы попытаться воспроизвести эту проблему, работающую в режиме выпуска, а не под VS?

0 голосов
/ 22 июня 2009

Я пытался воспроизвести вашу проблему, но на моей машине это не произошло.

Диспетчер задач показывает размер рабочего набора, который не является точным представлением использования памяти программой. Попробуйте вместо этого использовать perfmon.

  1. Пуск -> Выполнить -> perfmon.msc
  2. Добавить .NET CLR Memory / # Байт во всех кучах для вашего приложения

Теперь повторите эксперимент и проверьте, продолжает ли работать этот счетчик. Если это не так, это означает, что у вас нет утечки управляемой памяти.

0 голосов
/ 15 июня 2009

Рассмотрите возможность освобождения этого дескриптора файла. Также рассмотрите возможность использования оператора using вместо вызова IDisposable.Dispose (без каламбура).

0 голосов
/ 14 июня 2009

GC.Collect() само по себе не будет собирать все, вам нужно запустить:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Кроме того, я обнаружил, что среда выполнения не всегда освобождает собранную память сразу, вы должны проверить фактический размер кучи, а не полагаться на диспетчер задач.

0 голосов
/ 05 июня 2009

Убедитесь, что родительский элемент FlowDocument не висит, см. здесь . «Создание FlowDocument автоматически порождает родительский FlowDocumentPageViewer, в котором размещается контент». Если этот элемент управления находится вокруг него, это может стать источником вашей проблемы.

...