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:
DrawingGroup
is derived fromDependencyObject
, andDependencyObject
's constructor usesDispatcher.CurrentDispatcher
, which then creates a newDispatcher
for this thread.- The new dispatcher allocates some Win32 resource.
- Looking at
HwndWrapper
's finalization code in Reflector, I thinkHwndWrapper
tries to synchronize it's own cleanup usingDispatcher.BeginInvoke
. - 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
orTasks
instead of creating threads by hand delays this problem. But theThreadPool
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 everyThreadPool
thread. Without writing my ownThreadPool
, that is...