5

In my application, I create Freezable objects in background (thread pool) threads, freeze them and later display them on the main thread. Everything works, except after some time, the whole system becomes sluggish and the application eventually crashes.

I have managed to reduce the problem to this line:

var temp = new DrawingGroup();

If you run that often enough on different background (non-UI) threads, the whole system becomes sluggish, and eventually the application crashes.

(In my real application, I then draw something to this object, freeze it and later display it on the main thread, but that's not necessary to reproduce the problem.)

Full code to reproduce that problem (copy into a default blank wpf application):

public partial class MainWindow : Window
{
    private DispatcherTimer dt;

    public MainWindow()
    {
        InitializeComponent();

        dt = new DispatcherTimer();
        dt.Interval = TimeSpan.FromSeconds(0.1);
        dt.Tick += dt_Tick;
        dt.IsEnabled = true;
    }

    private int counter = 0;
    void dt_Tick(object sender, EventArgs e)
    {
        for (int i = 0; i < 100; i++)
        {
            var thread = new Thread(MemoryLeakTest);
            thread.Start();
        }

        Title = string.Format("Mem leak test {0}", counter++);

    }

    private void MemoryLeakTest()
    {
        try
        {
            var temp = new DrawingGroup();
            temp.Freeze();
        }
        catch (Exception e)
        {
            dt.IsEnabled = false;
            MessageBox.Show(e.Message+Environment.NewLine+e.StackTrace);
        }
    }
}

After ~150 timer runs (i.e. after about 15000 threads were created for a short time), I get this exception:

Not enough storage is available to process this command
   bei MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
   bei System.Windows.Threading.Dispatcher..ctor()
   bei System.Windows.DependencyObject..ctor()
   bei System.Windows.Media.DrawingGroup..ctor()
   bei WpfApplication5.MainWindow.MemoryLeakTest() in ...

What I think is happening is this:

  1. DrawingGroup is derived from DependencyObject, and DependencyObject's constructor uses Dispatcher.CurrentDispatcher, which then creates a new Dispatcher for this thread.
  2. The new dispatcher allocates some Win32 resource.
  3. Looking at HwndWrapper's finalization code in Reflector, I think HwndWrapper tries to synchronize it's own cleanup using Dispatcher.BeginInvoke.
  4. Since this background thread never starts a message loop, the cleanup code will never get called => Resource leak

Is there a way to solve or work around this problem?

What I have tried so far:

  • Obviously, using the ThreadPool or Tasks instead of creating threads by hand delays this problem. But the ThreadPool creates and shuts down new threads over time, too, so that only delays the problem, it's not a solution.
  • Forcing a full GC collect at the end of each thread didn't change anything. This isn't about garbage collection indeterminism.
  • Calling Dispatcher.InvokeShutdown manually at the end of the background thread seems to work, but I don't see how I could ensure it gets invoked at the end of every ThreadPool thread. Without writing my own ThreadPool, that is...
VMAtm
  • 27,943
  • 17
  • 79
  • 125
Niki
  • 15,662
  • 5
  • 48
  • 74

2 Answers2

3

This is a known defect of the Dispatcher system design in .NET. It affects WPF and non-WPF libraries that relies on Dispatcher. Microsoft has indicated that this will not be fixed.

It is not tied to the use of freezing operation, or any operation whatsoever. Any object (class) that is derived from DependencyObject, will have a base constructor that triggers the creation of the Dispatcher instance for that thread, if it hasn't been created before. In other words, Dispatcher is a thread-local singleton by design.

The crash happens when enough (tens of thousands) of instances of Dispatcher have been leaked. This implies that the same number of threads had been created and destroyed through the application's lifetime, each of which having created one or more DependencyObject's. Ask any application developer and they'll say that it would be uncommon, though not bad per se, but definitely needs some special care to design an application that have many threads to have been created and destroyed.


Before beginning, here is a safe approach to query the Dispatcher, without causing one to be automatically created if it didn't exist before.

Thread currentThread = Thread.CurrentThread;
Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);

MSDN: Dispatcher.FromThread method


Firstly, you can shutdown the dispatcher after you're done with the thread.

MSDN: Dispatcher.InvokeShutdown method


Secondly, realize that once it has been shut down for one thread, it is impossible to re-initialize Dispatcher for that same thread. In other words, after the InvokeShutdown, it is not possible to use WPF or any other library dependent on Dispatcher on that thread. The thread is effectively poisoned to death.


Combining the first and second points leads one to conclude that you need your own thread pool, each of which is endowed with a Dispatcher. As long as you are in control of the thread pool's wind down, there is no danger of leaking.


There are popular open-source .NET thread pool libraries which can run alongside (independently of) the .NET system thread pool. It is an appropriate way of solving this particular platform issue.


If you are in control of both the front-end (presentation layer) and back-end (image rendering), there is a simpler, draconian, and effective (though under-utilizing) approach:

  • Makes it a policy that the caller must initialize the Dispatcher; the back-end will merely check that the dispatcher already exists (via Dispatcher.FromThread), and refuse to do work if it isn't.

This approach shifts the burden to the presentation layer, which ironically tends to have the Dispatcher already initialized.

This approach is also applicable for a thread pool of one.

John Cummings
  • 1,949
  • 3
  • 22
  • 38
rwong
  • 6,062
  • 1
  • 23
  • 51
  • Thanks for this detailed answer! Sidenote: My application's lifetime is indefinite - it runs 365 days a year, 24 hours a day. So even if the thread pool only occasionally creates new threads, eventually it will crash, that's why this is a problem for me. Do you have a source for the "known bug / won't be fixed"-statement in the first paragraph? Are there OS thread pool alternatives you would personally recommend? – Niki Dec 11 '15 at 08:10
1

Am I right that you've run this logic with TPL or ThreadPool?
If so, and the last case is an option for you, you can easily get the DrawingGroup's Dispatcher property, and invoke it's InvokeShutdown method in finally block.

So you can write something like this:

DrawingGroup temp;
try
{
    temp = new DrawingGroup();
}
finally
{
    // do the work
    if (temp != null)
    {
        temp.Dispatcher.InvokeShutdown();
    }
}
VMAtm
  • 27,943
  • 17
  • 79
  • 125
  • +1 Good idea, but I'm not sure it's "legal": The ThreadPool thread might execute the same code again, but it will not (always) create a new `Dispatcher` - it might use the "disposed" instance. I'm not sure what `DrawingGroup` (or any `Freezable`) needs a `Dispatcher` for, but isn't it dangerous to use it with a half-cleaned up `Dispatcher`? – Niki Aug 21 '15 at 14:31
  • You should clear the `Dispatcher` at the moment you don't need the `DrawingGroup`. I'm sure that in `constructor` of it there is a logic for checking the `Dispatcher`, so I can't see any harm here. Try to look on code of it on http://referencesource.microsoft.com/ – VMAtm Aug 21 '15 at 15:15
  • No, I'm worried about the *next* `DrawingGroup` I create on the same thread. That one will get a half-destroyed `Dispatcher`. And looking at every access to `DispatcherObject.Dispatcher` in the reference source is a bit more work than I had in mind... – Niki Aug 21 '15 at 15:48
  • I think that the *next* `DrawingGroup` will create a new one Dispatcher, if old one is shutdown, but right now can't proof my words. – VMAtm Aug 21 '15 at 20:13
  • No, it won't, that's what I've been trying to say all the time. I've checked in the source code and in the debugger. If you create a new `DrawingGroup` after `InvokeShutdown`, the new DG gets the same half-destroyed `Dispatcher` object, in exactly the same state `InvokeShutdown` left it. – Niki Aug 21 '15 at 21:45
  • Too bad in this case. But vise versa, `ThreadPool` should not create a thread for each task you gave to it. May be you can use the only one `Freezable` object with dynamic content in it? – VMAtm Aug 21 '15 at 21:47
  • But... if I freeze the `Freezable`, it can't be dynamic. And if I don't freeze it, I can't use it on another thread. ThreadPool: Sure, it takes much longer for this problem to appear with TP threads, but for a application that's running 24/7, "takes longer" doesn't help me (much). – Niki Aug 21 '15 at 22:21
  • May be you can hide it instead of freezing? And after that use the only default dispatcher? – VMAtm Aug 21 '15 at 23:22
  • Freezing is completely unrelated to (*"happens-after"*) the problem. It is a class (type)'s inheritance from DependencyObject, and the constructor of an instance of that type, that causes the Dispatcher to be created for that thread. – rwong Dec 11 '15 at 03:49