I'm implementing INavigation in my MVVM Light Toolkit based Windows WPF application. This is implementation of NavigateTo:
private Frame GetFrame()
{
return (Application.Current.MainWindow as Generator.MainWindow).MainFrame; ;
}
public void NavigateTo(string pageKey)
{
CurrentPageKey = pageKey;
var obj = Activator.CreateInstance(_keyPageTypeMapping[pageKey]);
GetFrame()?.Navigate(obj);
}
When I navigate to new page, the new page is created and displayed. However, when I navigate away, I don't see that the old page's destructor has been called. Even after 10-20 navigations I don't see it. I only see desctructor is called after I exit application. Question is what is the best way to dispose old page?
What you are doing should be fine. If the Garbage Collector has not disposed of the pages yet then there hasn't been a need to do so yet. If you are concerned with if these old pages can be Garbage Collected, or if you just want to force the collection to happen. Try explicitly calling the Garbage collector with:
GC.Collect();
Related
We are currently reworking a WindowsForms application in WPF.
The software is quite large and it will take years to finish it, so we have a hybrid system that displays pure WPF for the new panels and Hosted WindowsForms elements in WindowsFormsHosts controls.
We use Syncfusion's WPF Docking Manager to show these pages in tabs (not sure that this info is relevant).
We spent quite a lot of time to track down memory leaks (using JetBrains' DotMemory), but we run out of memory after opening and closing nearly 100 pages containing WindowsFormsHosts.
This memory leak is quite strange, as you can see in the memory profiling, it seems that the problem lies in the unmanaged memory.
DotMemory profiling
The WindowsFormsHosts seem to be correctly disposed as well as the Child content.
As suggested here WPF WindowsFormsHost memory leak, we wrap the WindowsFormsHosts in a grid that we clear when we want to dispose it:
public override void Dispose()
{
if (this.Content is Grid grid && grid.Children.Count == 1)
{
if (grid.Children[0] is KWFHost wfh)
{
wfh.Child.SizeChanged -= ControlSizeChanged;
wfh.Dispose();
}
grid.Children.Clear();
}
base.Dispose();
}
and
public class KWFHost : WindowsFormsHost
{
protected override void Dispose(bool disposing)
{
if (this.Child is IDisposable disposable)
{
disposable.Dispose();
}
this.Child = null;
base.Dispose(true);
}
}
We suspect that the Hosting causes the leak because in DotMemory, in memory allocation we can see this:
memory allocation
Is there any known issue with the WindowsFormsHosts that could explain this? Or a way for us to isolate the source of the problem?
Edit : Here is the code that adds the Grid and WindowsFormHost :
public void SetContent(System.Windows.Forms.Control control)
{
var host = new KWFHost();
host.Child = control;
control.SizeChanged += ControlSizeChanged;
var grid = new Grid();
grid.Children.Add(host);
this.Content = grid;
}
I finally figured it out:
After a bit more digging, I found out that the WindowsFormsHosts are kept alive.
DotMemory gives me two reasons for that.
the first one: System.Windows.Interop.HwndSourceKeyboardInputSite. For a reason that I can't explain, there is still a reference to the WindowsFormsHost in the ChildKeyboardInputSinks in the HwndSource. If someone knows why, I'd be interested in hearing the reason.
I don't know if there is a better way to get rid of this reference, but here's the code I wrote:
private void CleanHwnd(KWFHost wfh) //my implementation of WindowsFormsHost
{
HwndSource hwndSource = PresentationSource.FromVisual(wfh) as HwndSource;
//here I isolate the right element to remove based on the information I have
IKeyboardInputSink elementToRemove = hwndSource.ChildKeyboardInputSinks.FirstOrDefault(c => c is KWFHost h && h.Parent is Grid g && g.Parent is KWindowsFormsHost fh && Equals(fh.TabName, TabName));
//The method CriticalUnregisterKeyboardInputSink takes care of removing the reference but as it's protected I use reflection to get it.
var mi = typeof(HwndSource).GetMethod("CriticalUnregisterKeyboardInputSink",
BindingFlags.NonPublic | BindingFlags.Instance);
//here I remove the reference
mi.Invoke(hwndSource, new object[] { elementToRemove.KeyboardInputSite });
}
I execute this method just before disposing the WindowsFormsHost.
The second reason was RootSourceRelationManager.
This one gave me a headache. So I did a "Full" dotmemory profiling (before it was a sampled one).
The result was strange, the memory leak disappeared.
The full profiling needs to be executed from Dotmemory and not from Visual Studio. That was the only difference.
After some research I found out that Microsoft.Visual.Design Tools.WpfTap.wpf was involved.
So it seems (I can't be sure) that executing from visual studio causes a memory leak (which is not that serious but a good thing to know).
In the end, after releasing a new test version of the software with the clean method, I no longer have a memory leak.
I was wondering if there is a more efficient way of opening a fresh window in WPF than how presented in code below :
WindowConfigureDatabase windowConfigureDatabse;
private void ButtonConfigureDatabase_Click(object sender, RibbonControlEventArgs e)
{
if (windowConfigureDatabase == null)
{
windowConfigureDatabase = new WindowConfigureDatabase();
}
windowConfigureDatabase.Clear();
windowConfigureDatabase.Show();
windowConfigureDatabase.WindowState = WindowState.Normal;
}
Where windowConfigureDatabase is the new window I want to open. windowConfigureDatabase.Clear(); just resets all the values to default - there aren't many of them to reset. I was wondering whether or not this is the proper way of opening new windows in wpf. The other path I was thinking of was just simply creating a new window on each button click (that way I don't have to clear values each time...) but I'm afraid of allocating too much memory if a user opens the window and closes it a lot of times as I'm not quite sure if garbage collector picks the window up on OnClose event.
So basically my question is - does the garbage collector pick my windows up after I close them during Closing/Closed event? If not, what would be the proper way of managing the window's memory manually? Would adding a
windowConfigureDatabase = null
on Closed/OnClosing event do well?
does the garbage collector pick my windows up after I close them
during Closing/Closed event?
Yes, if unreachable. Read up on this for a better idea.
Would adding a
windowConfigureDatabase = null
on Closed/OnClosing event do well?
Yes. Failing to do this will prevent the window from being garbage collected until windowConfigureDatabase is overwritten or the object containing it is collected.
The memory used by a window depends on its dimensions and how much memory it allocates to do what it needs to do. You generally don't need to worry about this unless you're creating tons of windows(~30+) and/or large volumes of data.
The fastest way to allocate is to allocate up front(ideally at startup) and reuse when possible. Fortunately with windows this is relatively easy. The idea is to hide instead of close, and only close when truly no longer needed.
Like this:
// In each window class or as a base class:
private bool isClosable = false;
protected override void OnClosing(CancelEventArgs args)
{
// Prevent closing until allowed.
if (!isClosable) {
args.Cancel = true;
Hide();
}
base.OnClosing(args);
}
// Call this when you want to destroy the window like normal.
public void ForceClose()
{
isClosable = true;
Close();
}
I'm having an issue with regards on memory management with our system.
Basically here is my design:
Load the List of the Records.
frmBrowse Memory used
90MB
After clicking record, it will pop another form which is the detailed form of the record selected
frmAP
Memory used 110MB
private void ViewRecord()
{
try
{
if(oAP!=null)
oAP = new frmAP();
oAP.LoadRecordDetails();
oAP.Show();
}
catch (Exception ex)
{
clsClass.oGenMethods.ErrorMessage(ex.Message, "frmBrowse", "EditSearchFields");
}
}
When the user CLOSE, i actually Hide it (frmAP )to prevent re-query datas such as lookup tables and others - frmAP - Memory used 110MB
On the frmAP_Close()
this.Hide();
-Memory at 110MB
After hiding the form (frmSomeForm ) the memory remains at 110MB
When the user close the Form(frmBrowse) where "frmAP" was called/instantiate
public void tsClose_Click(object sender, EventArgs e)
{
if (oAP != null)
{
oAP .Dispose();
oAP .Close();
GC.Collect();
}
}
The Problem is the Memory is Still at ~110MB Whether the List(frmBrowse) Form and the Record form(frmAp) was closed.
Please advice
Thanks in Advance
UPDATE:
i Tried to delete .Hide() to isolate the problem, but still the memory keeps growing
Calling Close does not release the memory associated with an object. That's the job of the garbage collector. Close or Dispose simply tells the object to release any unmanaged memory or resources it is using. The object, and everything it's referencing, still stays in memory.
To ensure oAP is collected, set it to null:
oAP.Close();
oAP = null;
However, this is likely the wrong thing to do. The whole point of a garbage collector is to collect memory from unreferenced objects when it's needed. So let it do its job, don't force it to run, you don't need to set oAP to null, and oAP and everything it references will be collected when the GC needs to.
I have a first form that calls another one and then disappears (frm.visible = false).
This form should not come back after being called once, but it's the main form (the first one that appears when you launch the program). I am trying to make it so when you close the second form, both forms close, I have tried multiple things but all of them leave the process active.
This is the code that I use :
private void frmCreation_FormClosing(object sender, FormClosingEventArgs e)
{
frmProperties frm = new frmProperties();
frm.Dispose();
}
//I have also tried frm.Close() which also does not work
This code close the two forms, but the process remains active. How do I counter this?
Application.Exit() tells your App to close itself.
Environment.Exit(0) tells Windows to kill it.
I prefer the latter since it really closes your app no matter what.
Maybe you are looking for Application.Exit()?
I can believe the solution here is : Do not handle it.
If the process is still pending that means you are not disposing your resources properly.
Using Application.Exit() or asking the system to do it Environment.Exit(0) may be logged in the system as an error occurred and you are better to know how to properly close a process than relied on Application.Exit(), if you want to close a thread of your app you have to know how to collect those garbage.
You can re-implement the Dispose method to Dispose services, sockets, streams, almost everything that has a .Dispose available.
public class MyClass: IMyClass, IDisposable
{
private bool _disposed = false;
// ...
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// dispose your stuff you created in this class
// do the same for other classes
// some examples
/*
_webClient.Dispose();
_connector.DataAvailable -= ConnectorHasDataComing
_socket.Dispose();
_timer.Dispose();
_taskLogs.ForEach(x => {
x.Token.Cancel();
x.Task.Wait();
x.Task.Dispose();
});
*/
}
// dispose native events
_disposed = true;
}
If you use System.Threading.Thread or System.Threading.Tasks.Task or System.IO.MemoryStream (or other kind of Stream - Writer/Reader), and others that requires a CancellationTokenSource. If you created the ressource in the class when you are disposing the class, use the Token.Cancel() method to let it know its parent is being disposed and .Wait() for it before calling .Dispose()
public async Task Run(CancellationTokenSource cancellationTokenSource)
{
// ...
while (Running) {
if (cancellationTokenSource.IsCancellationRequested) return;
// ....
}
// ....
using (var reader = new WaveFileReader(tempFile))
{
reader.Position = 0;
await reader.CopyToAsync(fileWriter,81920, cancellationTokenSource.Token);
}
}
I found my issue using the Diagnostic Tools when my Debug was still pending on something after closing the app.
If you use CPU Usage you can click on Break All and it set a breakpoint.
You can then see profiler and find what are your top functions, you might find out that your form is disposed but you have a Thread or Task that invokes fields on your form.
For my case, I was using a filewriter and I implemented IDisposable in that class but it sometimes was about or actual doing a transfer of data between a filereader and itself using .copyTo so it was pending without throwing an exception.
After clicking on one the events, click on Go to Source code and place a breakpoint, you may see events that your code is stocked on.
Otherwise, you can use in the same tool the tab Memory Usage to take a snapshot and look at the Heap and Objects diff or the tab CPU Usage and look at a recorded Profile. If find my copyTo issue that way.
You can also run your app with Throw on all exceptions
while disposing make sure no one recalls the form or its instance.
Also, if you are using the form event _FormClosing
Make sure if you have a modal to cancel the form closing, return and set e.Cancel = true; but do not set e.Cancel = true if the form is closing. And do not call this.Close() in a _FormClosing() event that you are handling yourself.
After, you may .Dispose() your stuff, but make sure no Dispose methods call the form back like invoking components, since they are being disposed or already disposed.
For people that use the hack that sets the form in an var instance to have access to it anywhere, do not dispose it, otherwise you are disposing a form already disposed.
I've got a simple web application using ASP.NET MVC3 and Ninject.Web.MVC (the MVC3 version).
The whole thing is working fine, except when the application ends. Whenever it ends, the kernel is disposed, as seen in Application_End() in NinjectHttpApplication:
Reflector tells me this:
public void Application_End()
{
lock (this)
{
if (kernel != null)
{
kernel.Dispose();
kernel = null;
}
this.OnApplicationStopped();
}
}
What happens is that my webserver goes down with a StackOverflowException (I tried both IIS7 and the built-in webserver in VS2010). I can only assume this is where it's going wrong, as I haven't written any code myself on application end.
I figured out that the Kernel knows how to resolve IKernel (which returns the Kernel itself), might this be something that could cause the stack overflow? I could imagine something like this happens:
Kernel.Dispose()
Dispose all instances in the kernel
hey! look at this, the kernel is also in the kernel. Return to step 1.
In other words, the kernel gets disposed, disposes all references it holds (which includes a self-reference), which causes it to dispose itself.
Does this make any sense?
Edit:
It seems the problem is in NinjectHttpApplication. Take a look at this activation code:
public void Application_Start()
{
lock (this)
{
kernel = this.CreateKernel();
...
kernel.Bind<IResolutionRoot>().ToConstant(kernel).InSingletonScope();
...
}
}
It seems ok, but what's happening now is that whenever an IResolutionRoot is called, kernel is cached within itself. When disposing the kernel, the cache is emptied which disposes all cached objects, which causes a circular reference.
A simple solution for NinjectHttpApplication would be to simply change the binding. Change the constant binding to a method one:
kernel.Bind<IResolutionRoot>().ToConstant(kernel).InSingletonScope();
becomes
kernel.Bind<IResolutionRoot>().ToMethod(x => this.Kernel);
This solves the problem, but I am not sure if the whole circular dispose caching issue is a bug in ninject.
I encountered the same issue.
I ended up copying the code for NinjectHttpApplication and removing Kernel.Dispose() in the Application_End function.
public void Application_End()
{
lock (this)
{
if (kernel != null)
{
//kernel.Dispose();
kernel = null;
}
this.OnApplicationStopped();
}
}
That should fix the error. Not sure if there is a planned fix for it though.
There was a bug in MVC3. It's fixed in the latest revision and will be part of the RC2 comming next week. In the mean time take the build from the build server http://teamcity.codebetter.com