TX Text Control .NET for WPF

Memory Usage and Resource Disposal

Topic

The garbage collector of .NET frees objects that nobody refers to any more, but it doesn't decide when a ServerTextControl gives back what it holds in Windows. That is the job of your code, and in a WPF application that runs for a whole working day, an instance that is never disposed adds up. What follows shows what each part of TX Text Control keeps in memory, which ones your code has to dispose, and how to find the instances that are left behind before Windows runs out of handles.

What Each Part Keeps

PartKeepsWhat your code does
The WPF TextControl in a windowIts document, the undo buffer and what it renders for the screenNothing to dispose: the control doesn't implement IDisposable. Keep the undo buffer small, as described below
A ServerTextControlIts document, without undo buffer and without renderingDispose it, in a using block
A MailMergeNo document of its own; it merges into the document of its TextComponentDispose it; it is a component
Byte arrays and copies of documentsWhatever your code stores in themDrop the reference when the copy isn't needed any more

A ServerTextControl keeps no undo history and renders no images, and Text Control names its smaller memory footprint as one of its advantages over a visible control. Documents the user doesn't see are therefore best created there, as Using ServerTextControl in Desktop Applications describes.

Dispose Every ServerTextControl

Text Control states that a ServerTextControl must be disposed explicitly and recommends a using block, which disposes the instance even when an exception leaves it; Create and Dispose Each Instance shows the pattern.

Warning

Undisposed Instances. When instances aren't disposed, Create eventually returns false. The most typical cause is that no new window handle can be created, because Windows allows a process 10,000 USER handles and 10,000 GDI handles. The instance then exists but can't be used, and loading a document into it fails with "The TXTextControl object must have been completely loaded to use this method." Text Control describes such failures as appearing after days and sporadically, and names instances that weren't disposed as their most likely cause. A restart of the application releases the handles; disposing the instances prevents the problem.

A MailMerge that your code creates is disposed the same way, in a using block, as the samples of Mail Merge Performance show.

Watch Instances and Handles

To find instances that are never disposed, count them. The following class, based on Text Control's diagnostics sample, creates ServerTextControl instances, counts those not yet disposed and reads the USER and GDI handles of the process. Add it to your project as ServerTextControlFactory.cs:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using TXTextControl;

// Creates ServerTextControl instances and counts those that aren't disposed yet
public static class ServerTextControlFactory
{
    private static int liveInstances;

    [DllImport("User32")]
    private static extern int GetGuiResources(IntPtr hProcess, int uiFlags);

    public static int LiveInstances => liveInstances;

    // 0 counts GDI objects, 1 counts USER objects
    public static int GdiHandles => GetGuiResources(Process.GetCurrentProcess().Handle, 0);
    public static int UserHandles => GetGuiResources(Process.GetCurrentProcess().Handle, 1);

    public static ServerTextControl Create()
    {
        ServerTextControl tx = new ServerTextControl();

        if (!tx.Create())
        {
            tx.Dispose();
            throw new InvalidOperationException("ServerTextControl could not be created.");
        }

        Interlocked.Increment(ref liveInstances);
        tx.Disposed += (sender, e) => Interlocked.Decrement(ref liveInstances);

        return tx;
    }
}

Create your instances through the factory, and show the counters, for example in a TextBlock named statusText at the bottom of MainWindow.xaml:

private void Convert_Click(object sender, RoutedEventArgs e)
{
    using (ServerTextControl tx = ServerTextControlFactory.Create())
    {
        tx.Load("contract.docx", StreamType.WordprocessingML);
        tx.Save("contract.pdf", StreamType.AdobePDF);
    }

    statusText.Text = $"Instances: {ServerTextControlFactory.LiveInstances}, " +
        $"USER handles: {ServerTextControlFactory.UserHandles}, " +
        $"GDI handles: {ServerTextControlFactory.GdiHandles}";
}

The code works as follows:

  • As a component, ServerTextControl raises its Disposed event when it is disposed, and the handler lowers the counter. Interlocked keeps the count right when background threads create instances.
  • GetGuiResources of User32.dll returns the number of GDI objects or USER objects of the process, the two resources Windows limits.
  • A counter that stays above zero after the work is done, or handle counts that keep rising, point to instances that are never disposed.

The blog article Diagnostics: When Calling ServerTextControl.Create() Failed contains the original sample, which also lists the live instances and can dispose all of them while you debug.

Keep the Undo Buffer Small

The WPF TextControl records every change so that the user can undo it, and the buffer grows with each change. Switch it off while your code generates a document, and empty it after your code has loaded one; Undo and Redo shows how.

When Memory Runs Out

  • Errors while loading or saving. A FilterException whose Reason is OutOfMemory means that TX Text Control couldn't allocate enough memory; FileTooBig means that the input file is too big. Exceptions and Error Handling shows how to catch a FilterException.
  • A 32-bit process. A 64-bit build has far more memory available; 32-bit and 64-bit Applications shows how to configure it.
  • Documents in view models. A document kept as a byte array in a view model, a list of recent files or an auto-save copy stays in memory as long as something refers to it. Store such copies in a file or a database when they aren't needed right away.

Next Steps