2009-06-04 4 views
8

Je tente actuellement de résoudre un problème lié à la libération d'une ressource FlowDocument. Je charge un fichier rtf et le place dans un FlowDocument avec TextRange.Load. J'ai remarqué qu'après cela, cela tient sur ces ressources et GC ne le collecte pas. J'ai couru un profileur de mémoire et ai vu que c'est vrai. Je l'ai également rétréci jusqu'à ce que je charge effectivement mettre le RTF dans le FlowDocument. Si je ne fais pas ça, alors tout va bien. Donc, je sais que c'est le problème.Problème de mémoire FlowDocument en C#

J'espère obtenir quelques conseils sur la façon dont je peux résoudre ce problème. Voici le code qui charge le RTF et tout. J'ai commenté tous les autres codes et même le mettre dans sa propre portée ainsi que essayé GC.Collect(). Toute aide est grandement appréciée.

EDIT: Voici mon code en entier pour le moment. J'ai sorti tout le reste, sauf l'essentiel pour le faire fonctionner. Le problème est toujours là. Comme vous pouvez le voir, FlowDocument et TextRange ne sont référencés nulle part ailleurs.

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(); 
    } 

J'ai trouvé this post, que j'espérais me aider à résoudre mon problème mais je pas eu de chance avec elle. Tout type d'aide est grandement apprécié. Je vous remercie.

EDIT: Je pense que je devrais mentionner la façon dont je vérifie cela. J'ai Windows Gestionnaire de tâches ouvert et je regarde l'utilisation de la mémoire utilise le processus de mon application. Quand je cours le code ci-dessus l'application va de 40.000K à 70.000K en faisant le TextRange.Load() (c'est un grand RTF de 400 pages) et une fois que cela finit il descend à 61.000K et reste là. Je m'attends à ce qu'il redescende à 40 000K ou du moins très proche.

Comme je l'ai mentionné plus tôt, j'ai utilisé un profileur de mémoire et j'ai vu qu'il y avait beaucoup de paragraphes, Run..ect. objets encore vivants après.

Répondre

0

Assurez-vous que le parent du FlowDocument ne traîne pas, voir here. "L'instanciation d'un FlowDocument engendre automatiquement un parent FlowDocumentPageViewer qui héberge le contenu." Si ce contrôle traîne, il pourrait être la source de votre problème.

+0

Je ne référence le parent nulle part. Donc, je ne pense pas que le FlowDocumentPageViewer est mon problème. J'ai rempli le code plus dans le message original. – Jasson

1

FlowDocument utilise System.Windows.Threading.Dispatcher pour libérer toutes les ressources. Il n'utilise pas de finaliseurs car les finaliseurs bloquent le thread courant jusqu'à ce que toutes les ressources soient libres. Ainsi, l'utilisateur peut voir certains cas de gel de l'interface utilisateur et ainsi de suite. Les répartiteurs s'exécutent dans le thread d'arrière-plan et ont moins d'impact sur l'interface utilisateur.
Appelez donc GC.WaitForPendingFinalizers(); n'a aucune influence sur cela. Vous avez juste besoin d'ajouter du code pour attendre et permettre aux Dispatchers de terminer leur travail. Essayez juste d'ajouter quelque chose comme Thread.CurrentThread.Sleep (2000/* 2 secs * /);

EDIT: Il me semble que vous avez trouvé ce problème lors du débogage de certaines applications. J'ai écrit le cas de test très simple suivant (programme de console):

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(); 
    } 

J'ai essayé de reproduire le problème. Je l'ai fait fonctionner plusieurs fois et ça fonctionnait parfaitement - il n'y avait pas de fuites (environ 3,2Kb tout le temps et 36 poignées). Je ne pouvais pas le reproduire avant d'avoir exécuté ce programme en mode debug dans le VS (juste f5 au lieu de ctrl + f5). J'ai reçu 20,5Kb au début, 31,7Kb après le chargement et avant GC et 31Kb après GC. Cela ressemble à vos résultats.
Alors, pourriez-vous s'il vous plaît essayer de reproduire ce problème en cours d'exécution en mode de libération pas sous le VS?

+0

Oui, j'ai remarqué que FlowDocument hérite de System.Windows.Threading.DispatcherObject. J'avais déjà essayé quelque chose de similaire et j'avais le même résultat qu'avant - rien. Cela valait la peine d'essayer si :). Thread.Sleep() est une méthode statique btw. – Jasson

+0

Oh, désolé, il était très tard et j'étais un peu endormi. Je vais essayer aujourd'hui de vérifier ce problème et de déboguer un peu. Cela devient très intéressant. – zihotki

+0

Merci et bonne chance. Je suis vraiment perplexe sur celui-ci mais je continue toujours à chercher une solution. – Jasson

0

GC.Collect() par lui-même ne recueillera pas tout, vous devez exécuter:

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

Aussi, j'ai trouvé le temps d'exécution ne sont pas toujours libérer la mémoire recueillie tout de suite, vous devriez vérifier la taille du tas réelle au lieu de s'appuyer sur le gestionnaire de tâches.

+0

J'ai essayé cela et cela m'a semblé aider un peu aussi bien que regarder le tas réel, qui était un peu mieux que ce que le gestionnaire de tâches montrait, mais dans l'ensemble le problème persiste. Je suis assez certain que c'est un type de fuite de mémoire; Cependant, je ne vois pas ce que c'est exactement. Le code que j'ai fourni ci-dessus est à ma connaissance complètement contenu et par conséquent je suis perplexe parce que je n'ai jamais eu affaire à quelque chose comme ça avant. D'autres suggestions? Merci du coup de main jusqu'à présent. – Jasson

0

Envisagez de libérer ce handle de fichier. Pensez également à utiliser l'instruction "using" au lieu d'appeler IDisposable.Dispose (sans jeu de mots).

+0

J'ai effectivement utilisé dans le code, je ne sais pas pourquoi je ne l'ai pas mis là-dedans. Je vais mettre à jour le code en une seconde. Quand vous dites que je devrais envisager de libérer la poignée de fichier voulez-vous dire le FileStream? Je ne pense pas. Je ne pense pas que je sois sûr de ce dont vous parlez. Merci pour l'aide. – Jasson

+0

Votre nouveau code est beaucoup plus propre.Considérez le fait que le code C# est compilé par JIT, donc une fois que vous exécutez quelque chose, votre jeu de travail devient plus grand. Maintenant, vous avez une fuite de mémoire si vous exécutez le même code encore et encore (c'est déjà JITted), et votre mémoire allouée se développe. Je ne suppose pas que vous ayez une fuite de mémoire avec le code ci-dessus. – GregC

+0

Je suis assez sûr que c'est une fuite de mémoire. J'ai fait un peu de jeu avec le code et j'ai constaté qu'une quantité importante de mémoire va à un RichTextBox si ce contrôle est réellement affiché dans la fenêtre avec du texte. Il semble que la plupart de cette mémoire n'est pas récupérée lorsque je ferme la fenêtre. En enlevant l'affichage RichTextBox, j'ai vu qu'il y a encore beaucoup de mémoire dans le FlowDocument qui n'est pas récupéré. C'est à peu près la même chose que le problème RichTextBox car il contient un FlowDocument. Mais la portée de ce FlowDocument est très contenue, c'est pourquoi je suis perplexe. – Jasson

0

J'ai essayé de reproduire votre problème, mais cela n'arrive pas sur ma machine. Le gestionnaire de tâches affiche la taille de l'ensemble de travail, ce qui n'est pas une représentation précise de l'utilisation de la mémoire d'un programme. Essayez d'utiliser perfmon à la place.

  1. Démarrer -> Exécuter -> perfmon.msc
  2. Ajouter la mémoire CLR .NET/# Octets dans toutes Heaps pour votre application

Maintenant, répétez l'expérience et vérifier si ce compteur ne cesse d'augmenter . Si ce n'est pas le cas, cela signifie que vous ne perdez pas de mémoire gérée.

+0

En utilisant perfmon, j'ai découvert que dans mon programme exemple, il semble que la première fois que je l'exécute, la mémoire n'est pas récupérée; cependant, les exécutions suivantes semblent récupérer la mémoire. Dans mon application actuelle, la mémoire n'est pas récupérée du tout. Je cours ce code dans une deuxième fenêtre. Peut-être que c'est la raison pour laquelle? Mais pourquoi récupérerait-il le souvenir seulement la première fois? J'ai également posé cette question sur les forums WPF MSDN. http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/15e2b42e-1975-4d68-8ddb-59bbcd1f0633/ – Jasson

+0

Avez-vous une référence à la deuxième fenêtre qui traîne? Avez-vous souscrit à des événements exposés par l'un des composants de la deuxième fenêtre à partir de la première fenêtre? –

6

Si j'ai confirmé qu'il y a une fuite de mémoire, voici ce que je ferais pour déboguer le problème.

  1. Installer les outils de débogage pour Windows à partir http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a
  2. incendie en Windbg à partir du répertoire d'installation.
  3. Lancez votre application et effectuez les opérations qui présentent une fuite de mémoire.
  4. Attachez Windbg à votre application (F6).
  5. Type de .loadby sos mscorwks
  6. Type de !dumpheap -type FlowDocument
  7. Vérifiez le résultat de la commande ci-dessus. Si vous voyez FlowDocuments multiples, pour chaque valeur de la première colonne (qui contient l'adresse), ne

Type de !gcroot <value of first column>

Cela devrait vous montrer qui est maintenant à la référence.

+1

Si vous utilisez .net 4.0+, remplacez l'étape 5 par '.loadby sos clr' – maxp

1

J'avais précédemment fait le # 7. C'était la première fois que j'utilisais Windbg et donc je ne savais pas quoi faire avec l'adresse pour trouver les références. Voici ce que j'ai.

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) 

(Je l'ai mis dans un bloc de code pour faciliter la lecture). Ceci est après que j'ai fermé la fenêtre. Donc, il semble que ce soit référencé par quelques choses. Maintenant que je sais cela, comment puis-je libérer ces références afin qu'elles puissent libérer le FlowDocument.

Merci pour votre aide. J'ai l'impression de gagner enfin du terrain.

3

Nous avons eu un problème similaire dans lequel nous créions un document de flux dans un thread différent, j'ai remarqué dans le profileur de mémoire que les objets étaient toujours là.

Pour autant que je sache, comme décrit dans ce link

« Lorsqu'un FlowDocument est créé, les objets de contexte de formatage relativement coûteux sont également créés pour elle dans son StructuralCache. Lorsque vous créez plusieurs FlowDocs dans une boucle serrée, un StructuralCache est créé pour chaque FlowDoc.Vous appelez Gc.Collect à la fin de la boucle, dans l'espoir de récupérer de la mémoire. StructuralCache a un finalizer qui libère ce contexte de mise en forme, mais pas immédiatement.Le finalizer planifie efficacement une opération pour libérer des contextes à DispatcherPriority.Background. " Donc, jusqu'à ce que les opérations du répartiteur soient terminées, le document Flow sera en mémoire. Donc l'idée est de compléter les opérations du répartiteur.

Si vous êtes dans un thread dans lequel Dispatcher est actuellement en cours d'exécution essayez le code ci-dessous, il forcera toutes les opérations dans la file d'être terminée, comme SystemIdle est la priorité la plus basse:

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

Si vous êtes dans un thread dans lequel Dispatcher ne fonctionne pas, comme dans mon cas, seul document de flux unique a été créé en fil, alors j'ai essayé quelque chose comme:

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

cela la file d'attente d'un arrêt à bout et puis exécutera répartiteur pour nettoyer le FlowDocument, puis à la fin, il fermera le répartiteur.