Reputation: 701
I have been reading up on WPF memory handling and have followed every top 5 and top 8 Memory leak Pitfalls, but nothing helps me in my current situation.
I have had an issue with my software where WPF won't release it memory until the program terminates. If I let it go forever it will cause an OutOfMemoryException no matter what I do. I have managed to isolate the issue within a small sample to show how it is not releasing its memory, even though I do not use it anymore. Here is how I can reproduce the problem:
I created 2 projects, one console program, and one WPF Application. In my WPF application I have a MainWindow.xaml which has nothing in it:
<Window x:Class="MemoryLeakWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MemoryLeakWpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="MainWindow_OnLoaded">
<Grid>
</Grid>
</Window>
I do subscribe to the Loaded event which I use to instantly close the window which can be seen in the .cs file here:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Debug.WriteLine("Constructing",GetType().Name);
}
~MainWindow()
{
Debug.WriteLine("Deconstructing", GetType().Name);
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
Close();
}
}
I also have added debug lines to my constructor and deconstructor so I can track when it is created and discarded. I then create a Controller class in the WPF application which represents the entry point to this WPF class library that has a method to create and show the window:
public class Controller
{
public void Execute()
{
MainWindow window = new MainWindow();
window.ShowDialog();
Debug.WriteLine("Constructing", GetType().Name);
}
~Controller()
{
Debug.WriteLine("Deconstructing", GetType().Name);
}
}
Here I also added debug track lines. I don't have an App.xaml as this WPF project is set as a Class Library in its properties. That is the WPF Part. In the console project I added the following code to my main class:
[STAThread]
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
Controller controller = new Controller();
Console.WriteLine("Test " + i);
controller.Execute();
}
Console.WriteLine("Pressing enter will close this");
Console.ReadLine();
Debug.WriteLine("Party is over, lets leave");
}
So basically the setup is that I have a console class that wants to show a dialog. It creates the controller for the WPF application and calls Execute. The controller show the window that immediately closes when it has done loading. The console class then creates a new controller to do the process all over again. Now, this is what I see in my output:
MainWindow: Constructing
Controller: Constructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
The controller is constructing and deconstructing, but the window is not. However, when the for loop is complete and I press enter to let the program run out, I get this:
Party is over, lets leave
MainWindow: Deconstructing
Controller: Deconstructing
MainWindow: Deconstructing
Controller: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
Suddenly all the instances of the MainWindow are now deconstructing, but only when the program runs out, not when we discard the reference in the for loop. This means that in our program we only have a finite number of times we can open the window before an OutOfMemoryException will occur.
But the million and a half dollar question is: How can I persuade WPF to release its memory while the program is running and not when the program closes?
Upvotes: 2
Views: 827
Reputation: 2754
You claim being an [STAThread]
yet you have no message pump. Without a message pump you are not truely STA. In this particular case this means WPF never gets the chance to clean up its resources. WPF is probably posting messages to the message queue which are never picked up.
Since WPF is a multithreaded system it has to perform background operations, including synchronizing between multiple threads. To get back to the main thread it uses the Dispatcher
infrastructure, which you have not setup correctly.
To fix your problem you need to be running a WPF Dispatcher
on the STA thread, not implementing your own loop.
Also, for completeness, link to a related post which brought me here. Make sure you measure the right thing after setting up the dispatcher infrastructure.
Upvotes: 1
Reputation: 701
So following Peter Duniho comment in the question I set out to test if a WindowService to reuse the windows would be useful and it did. Here is the very crude service I created in the sample project:
public class ViewFactory
{
private static ViewFactory _instance;
private MainWindow mainWindow = null;
private ViewFactory()
{
Debug.WriteLine("ViewFactory created");
mainWindow = new MainWindow();
}
public static ViewFactory Instance
{
get
{
if (_instance == null)
{
_instance = new ViewFactory();
}
return _instance;
}
}
public MainWindow GetMainWindow()
{
return mainWindow;
}
}
Now with this system, I needed to adjust my view, because I cannot close my window at any time, as this will release some resources and therefore I would not be able to reuse to the window. In the view I have to subscribe to the closing event:
<Window x:Class="MemoryLeakWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MemoryLeakWpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="MainWindow_OnLoaded" Closing="MainWindow_OnClosing">
<Grid>
</Grid>
</Window>
And in the code-behind file the handler looks like this:
private void MainWindow_OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
Visibility = Visibility.Hidden;
}
This handler stops any attempt to close the window and just hides it. When ShowDialog is called it will show it again. I have tested this for hours on my software and the memory is stable.
Upvotes: 1