TX Text Control .NET for Windows Forms

Memory Usage and Resource Disposal

Topic

Desktop applications often run all day, and every document they open, merge or convert passes through the memory of one process. TX Text Control releases what an instance holds when the instance is disposed, so most memory problems come from instances that are never disposed, and on Windows those show up as a limit on handles before they show up as a lack of memory. Find out what each part of TX Text Control keeps, how to release it and how to watch for instances your code leaves behind.

What Holds Memory

PartHoldsReleased when
A TextControl on a formIts document, the undo buffer and what it renders for the screenThe control is disposed, together with its form
A ServerTextControlIts document, without undo buffer and without renderingYour code disposes it
A MailMergeNo document of its own; it works on the document of its TextComponentYour code or the form disposes it
Byte arrays and copies your code keepsDocuments in the TX Text Control format, PDF files and other dataYour code removes the last reference

Each TextControl has its own document: a form with two editors keeps two independent documents. A ServerTextControl keeps no history for undo and redo and renders no images, which is why Text Control names a smaller memory footprint as one of its advantages over the visible control. Work the user doesn't see belongs there, as Using ServerTextControl in Desktop Applications describes.

Dispose Every ServerTextControl

A ServerTextControl must be disposed explicitly: create it in a using block, call Create first and check its return value, as Create and Dispose Each Instance shows.

Warning

Undisposed Instances. Instances that are never disposed accumulate until Create fails. The most common reason for a failing Create is that no new window handle can be created: Windows limits a process to 10,000 USER handles and 10,000 GDI handles. An instance whose Create returned false exists but can't be used; loading a document into it fails with the message "The TXTextControl object must have been completely loaded to use this method." Text Control reports that such failures often appear after an application has run for several days, and sporadically, and that the cause is usually instances that aren't disposed. Restarting the application releases the handles, but disposing the instances is the fix.

The same rule applies to a MailMerge that your code creates: it is a component, and a using block disposes it, as the samples on Mail Merge Performance do.

Count Instances and Handles

Text Control's diagnostics sample counts the ServerTextControl instances that aren't disposed yet, together with the USER and GDI handles of the process. The following class is based on it. 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 instances through the factory, and show the counters, for example in a Label named statusLabel after each task:

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

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

The code works as follows:

  • ServerTextControl is a component and raises the Disposed event when it's disposed. The handler decreases the counter; Interlocked keeps it correct when several threads create instances.
  • GetGuiResources from User32.dll returns the number of GDI or USER objects of a process, the two counts Windows limits.
  • After the using block, the instance counter is back where it was. A counter or a handle count that grows while the application runs points to code that doesn't dispose its instances.

The blog article Diagnostics: When Calling ServerTextControl.Create() Failed contains the complete sample, which also keeps a list of the live instances and can dispose all of them for debugging.

Keep the Undo Buffer Small

The editor records every change for undo and redo. AllowUndo switches this off; the reference recommends it when a TextControl is used for tasks such as generating reports, to save time and memory. Switch it on again before the user edits. After your code has loaded or created a document, ClearUndo empties the buffer. Undo and Redo shows both.

When Memory Runs Out

  • Errors while loading or saving. A FilterException with the Reason OutOfMemory means that TX Text Control couldn't allocate enough memory, and FileTooBig that the input file is too big. Exceptions and Error Handling shows how to catch them.
  • 32-bit processes. A 64-bit application has more memory available and can open and save much larger documents, as 32-bit and 64-bit Applications explains.
  • Copies your code keeps. A byte array with a document stays in memory as long as your code refers to it, for example in a list of recent documents. Keep such copies only as long as you need them, or store them in a file or a database.

Next Steps