How to ensure garbage collection when user closes a non-modal window? - c#

In my C# Winforms App, I have the following (minimal code shown)
Form1 is the main app that the user uses to do stuff. Form2 shows a help file that explains how to use the features on Form1 to do stuff. I want the user to be able to display (modeless) and close the help file at will so long as Form1 is visible.
I also worry about the memory leak that may occur as the user opens and closes Form2. So, when the user closes Form2, it raises an event that Form1 subscribes to. When the Form1 event method is invoked, is calls Dispose() on Form2, sets the Form2 object to null and calls the garbage collector.
Will this remove the chance for a memory leak caused by the user opening and closing Form2? Is it overkill? Is there a better way of ensuring garbage collection at the point in time that Form2 is closed? I don't want to rely on Windows doing it later when it decides to
UPDATE
Jimi pointed out that I don't need a custom event handler for the Form2 Closed event. He's right. In my Form1 class, I'm now using the standard FormClosedEventHandler for my Form2. The code itself, however remains pretty much the same. However, when I remove the call to GC.Collect(), I'm seeing evidence of a memory leak using Task Manager.
Here's my data:
Run #1. Form2_FormClosed method has:
------------------------------------
f_HelpForm.Dispose();
f_HelpForm = null;
GC.Collect();
Start App
Task Manager Memory for app: 6.7 MB
Open and Close Form2 20 times
Task Manager Memory for app: 8.2 MB
Run #2. Form2_FormClosed method has:
------------------------------------
f_HelpForm.Dispose();
f_HelpForm = null;
//GC.Collect();
Start App
Task Manager Memory for app: 6.9 MB
Open and Close Form2 20 times
Task Manager Memory for app: 18.9 MB
Run #3. Form2_FormClosed method has:
------------------------------------
//f_HelpForm.Dispose();
f_HelpForm = null;
//GC.Collect();
Start App
Task Manager Memory for app: 6.9 MB
Open and Close Form2 20 times
Task Manager Memory for app: 18.1 M
Without the call to GC.Collect(), and with or without the call to Dispose(), the footprint grows 100% bigger as compared to the code that includes the call to GC.collect().
I hear what you guys are saying, but .......... I think I'll leave my code in its "Run #1" configuration
The code
Note: I acknowledge that setting form2 = null has a direct influence on behind-the-scenes garbage collection. However, my purpose in setting form2 = null is to provide a signal to the Form2Button_Click method that it can use to decide whether or not to instantiate a Form2 object
public partial class Form1 : Form
{
Form2 form2;
public Form1()
{
form2 = null;
}
private void Form2Button_Click(object sender, EventArgs e)
{
if (form2 == null)
{
form2 = new ClsHelpForm(this);
form2.Form2Closed += Form2_FormClosed;
}
form2.Select();
form2.Show();
}
//When this user clicks the Help button on Form1, this method is invoked
private void Form2_FormClosed(object sender, EventArgs e)
{
form2.Form2Closed -= Form2_FormClosed;
form2.Dispose();
form2 = null;
GC.Collect();
}
{
public partial class Form2 : Form
{
public event EventHandler Form2Closed;
public Form2()
{
}
//When the user clicks the "X" button on Form2, this method is invoked
private void Form2_FormClosed(object sender, Form2EventArgs e)
{
Form2Closed?.Invoke(this, EventArgs.Empty);
}
}

I also worry about the memory leak that may occur as the user opens and closes Form2.
Why are you worried about a memory leak? Don't try to optimize when there's no evidence of a problem. As long as Form2 and all of its child objects actually clean up resources when Dispose is called, there should be no memory leak.
Is there a better way of ensuring garbage collection at the point in time that Form2 is closed? I don't want to rely on Windows doing it later when it decides to
This seems like unnecessary paranoia. Just be sure Form2 cleans up in its Dispose method and let garbage collection occur naturally. Everything looks fine except remove the GC.Collect() call.

A member instance of Form2 doesn't take a lot of room on the heap and there seems to be little cause to create and destroy its Handle every time the user wants to show it.
Why not just prevent the destruction of the Form2 handle until app closes?
public partial class Form2 : Form
{
public Form2(Form owner)
{
InitializeComponent();
Owner = owner;
StartPosition = FormStartPosition.Manual;
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if(Visible)
{
Location = new Point(
Owner.Location.X + Owner.Width + 10,
Owner.Location.Y);
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if(e.CloseReason.Equals(CloseReason.UserClosing))
{
e.Cancel = true;
Hide();
}
}
}
When the form2 is cycled (by automation) 100 times, monitoring the process memory shows zero GCs and no direct correlation to the number of times shown.
Where:
public partial class Form1 : Form
{
Form2 _form2;
public Form1()
{
InitializeComponent();
_form2 = new Form2(this);
Disposed += (sender, e) => _form2.Dispose();
buttonShowHelp.Click += async (sender, e) =>
{
for (int i = 0; i < numericUpDown.Value; i++)
{
_form2.Visible = true;
await Task.Delay(500);
_form2.Visible = false;
await Task.Delay(500);
}
// But leave it open after cycling.
_form2.Visible = true;
};
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
}
}

If you put trash in the kitchen garbage can, and 5 minutes later the trash is still there, it does NOT mean that the trash will be there forever. It just means that the trash hasn't been emptied yet. It will get emptied eventually once it gets full enough. A "leak" would be if you put your trash on the floor, in which case it would not get picked up when the trashcan gets full. (not a perfect analogy, since someone is (hopefully) going to pick it up at some point, but you get the idea)
Your observations probably show a 100% growth because there's no memory pressure to require a collection. A memory leak would only occur if that memory never gets freed by the GC, which forcing a garbage collection would not fix. An example of a memory leak would be a reference to an unmanaged resource that is not release when the form is disposed (not closed).
Calling GC.Collect yourself is not going to fix memory leaks - it just cleans up memory earlier that the system needs to.

Related

Application.Exit failing to completely exit program

I'm having an issue closing a program when there is a large amount of dialogs open. I was able to reproduce the bug using the following functions. The parent form on which this runs on is created using Application.Run(mainform).
When button1 is clicked, it creates a very basic form that opens another dialog of itself when "ok" is pressed. It then initiates the OnShutdown() function. This waits 15 seconds, then attempts to close all windows and exit the program. When more than 6 dummy windows are open, the function closes all the dummy windows but not the main window.
The steps for me reproducing the problem is:
Click button1
Close created heavy dialog
Click on on the msgbox
Click on button1 again
On the new heavy dialog, I press Ok to open another heavy dialog
Continue to open more heavy dialog, until 6-10 are open concurrently
Once the 15 seconds are up, all the dialogs close, but the parent form remains open, when it should be closed.
private void button1_Click(object sender,EventArgs e) {
BasicTemplate heavy = new BasicTemplate();
heavy.ShowDialog();
OnShutdown();
}
private void OnShutdown() {
timerSignals.Enabled=false;//quit receiving signals.
string msg = "";
msg+=Process.GetCurrentProcess().ProcessName+" ";
msg+=Lan.g(this,"will shut down in 15 seconds. Quickly click OK on any open windows with unsaved data.");
MsgBoxCopyPaste msgbox = new MsgBoxCopyPaste(msg);
msgbox.Size=new Size(300,300);
msgbox.TopMost=true;
msgbox.Show();
ODThread killThread = new ODThread((o) => {
Thread.Sleep(15000);//15 seconds
//Also happens with BeginInvoke()
Invoke((Action)(() => { CloseOpenForms(true); }));
Thread.Sleep(1000);//1 second
Invoke((Action)Application.Exit);
});
killThread.Start(true);
return;
}
public void Start(bool isAutoCleanup) {
_isAutoCleanup=isAutoCleanup;
if(_thread.IsAlive) {
return;//The thread is already running.
}
if(_hasQuit) {
return;//The thread has finished.
}
_dateTimeStart=DateTime.Now;
_thread.Start();
}
//Code for "OK" button on the heavy dialog created above
private void butOK_Click(object sender,EventArgs e) {
BasicTemplate formp = new BasicTemplate();
formp.ShowDialog();
}
private bool CloseOpenForms(bool isForceClose) {
for(int f=Application.OpenForms.Count-1;f>=0;f--) { //Count backwards to avoid out of bounds
if(Application.OpenForms[f]==this) {
continue;
}
Form openForm=Application.OpenForms[f];//Copy so we have a reference to it after we close it.
openForm.Hide();
if(isForceClose) {
openForm.Dispose();
}
else {
openForm.Close();//Attempt to close the form
if(openForm.IsDisposed==false) {
openForm.Show();//Show that form again
return false;
}
}
}
return true;
}
When line 3's "heavy.ShowDialog()" is changed to "heavy.Show()", the holdup no longer takes place. When multiple heavy dialogs are opened using a different button, the issue no longer takes place (up to ~20 new dialogs).
The shutdown sequence is run as a thread to allow the user to save any changes they have made before the program's database updates. I'm not sure if what I've implemented correctly shows the bug, but it at least produces similar events.

Disallowing interaction with background form

On my application's first run, two forms open. The topmost form needs to take priority, and disallow any interaction with the form in the background. I have tried ShowDialog() as referenced here, however this hides the form in the background which I do not wish to do. Is there a method of accomplishing this?
public Form1()
{
InitializeComponent();
if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
void firstrunactions()
{
//open the get-started form and invite user to populate serialisable objects
firstrun frwindow = new firstrun();
frwindow.ShowDialog();
}
When you are using .ShowDialog() the execution of the containing method is paused until you close the newly opened window. So make sure to do everthing else before you call .ShowDialog(). Otherwise your program gets stuck in this method. If you are calling .ShowDialog() before the background window is shown will cause problems.
But using .ShowDialog() here is totally correct and has the right functionality.
Example how not to do it (causes the same behavior like in your problem):
public Form1()
{
InitializeComponent();
//this is the wrong place for showing a child window because it "hides" its parent
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
The magical place where it works:
private void Form1_Shown(object sender, EventArgs e)
{
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
Edit: In your case it is enough moving if(!fileexistst...) into the Form1_Shown()-event.
Try with frwindow.ShowDialog(this);
Or instead "this" pass the other form as parameter.
Also move this part if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
in OnLoad override.

C# - ShowDialog(this) memory leak

I have sample code to Show new form dialog:
private void button1_Click(object sender, EventArgs e)
{
(new Form2()).ShowDialog(this);
GC.Collect();
}
If the form has buttons, panels labels etc. then Dispose method of this form2 is executed but if I add toolStrip then the method Dispose isn't executed. Why Dispose is executed in these some cases ?
I readed if form is showing by ShowDialog then I should execute Dispose method, but why it works sometimes without it ?
Edit:
Collect method can be added after ShowDialog. This method is only for tests and is executed multiple times.
To check if method Dispose was executed I added breakpoint (in debug mode). Of course Dispose of example with ToolStrip is executed when program is ending.
OK I know how to proper implemented it, for me was intrested why GC can't clean up if on the form is toolStrip ?
The most simple code to show it is:
Example 1 - result - 100,
Example 2 - result > 0 (GC can clean up),
Example 3 - always 0.
Why example 2 and 3 are that different ?
private class Form2 : Form
{
public static int disposed = 0;
byte[] data;
private System.Windows.Forms.ToolStrip toolStrip11;
public Form2(bool addToolStrip)
{
data = new byte[100000];
this.Shown += (sender, e) => { this.Close(); };
this.Controls.Add(new Button());
if (addToolStrip)
{
this.toolStrip11 = new System.Windows.Forms.ToolStrip();
this.Controls.Add(this.toolStrip11);
}
}
protected override void Dispose(bool disposing)
{
++disposed;
base.Dispose(disposing);
}
}
private void ShowResult()
{
GC.Collect(); GC.Collect();
GC.WaitForFullGCComplete();
MessageBox.Show(Form2.disposed.ToString());
Form2.disposed = 0;
}
private void button1_Click(object sender, EventArgs e)
{
//proper
for (int i = 0; i < 100; ++i)
{
using(Form2 f = new Form2(true))
{
f.ShowDialog();
}
}
ShowResult();
//ok GC can clean - why yes ?
for (int i = 0; i < 100; ++i)
{
Form2 f = new Form2(false);
f.ShowDialog();
}
ShowResult();
//GC can't clean - why not ?
for (int i = 0; i < 100;a ++i)
{
Form2 f = new Form2(true);
f.ShowDialog();
}
ShowResult();
}
Your code doesn't make sense - you're forcing a garbage collection cycle before making a call that you claim leaks memory.
You should only implement Dispose to get rid of unmanaged resources. For managed resources it simply doesn't make sense to try to beat the garbage collector in any common use case. Also, I'm not sure how you even think you're "leaking memory" since you cannot possibly predict or know when the GC has done its job properly - strictly speaking a .NET program only has a real memory leak if the GC refuses to clean it up properly. Since that is information undisclosed to the developer - even an explicit cleanup can, at its own discretion, elect not to collect all potential garbage - your claim is by definition unverifiable.
Many objects that are disposable also have a finalizer. When an object would normally be cleaned up by the GC it will first check if it has a finalizer that hasn't been run. If it hasn't, it goes into a queue for its finalizers to be run, and is then actually eligible for garbage collection.
Because of this mechanism it's possible for some disposable objects to have their resources cleaned up even if they are not explicitly disposed.
That said, this is an unreliable mechanism. You don't know when an object will be collected once it is eligible for collection, and the state that the object is in when the finalizer is run can result in unusual and undefined interactions with "itself".
You should avoid relying on a finalizer whenever possible and instead explicitly dispose of such disposable resources.

I have a system task tray application - Can I close the main form instead of just hiding it?

I have an application that has a main form and a system task tray icon. In the designer of the main form, I dragged the TrayIcon control on the form, so it is a child of the main form.
At this point, when the user presses the close button on the main form, it actually just hides it so that the application wont terminate, unless the user right clicks the TrayIcon and clicks exit. But, the main form has a lot of controls and resources, and when the main form is hidden, it still uses memory for those resources. My goal is to actually dispose of form so it doesn't take up that memory while it is not being used.
Unless I am mistaken, and when the main form is hidden it doesn't take up that memory anymore, but I don't think that is the case.
I'm no expert on memory, I may even be completely mistaken on how memory management works, and thus this question is invalid.
Anyways, if I am correct in that when the main form is only hidden it still takes up memory that can be freed by fully closing the form, is there a way for me to actually close the main form without the application terminating? If so, I would need to create the TrayIcon with code in the Program class instead of in the class of the main form, correct?
No, that's certainly not necessary. It is encouraged by the convenience of the designer but you can easily create an application that only creates a window on demand. You'll have to write code instead. It doesn't take a heckofalot, there's a sample app with basic functionality. Edit the Program.cs file and make it look similar to this (icon required, I called it "SampleIcon"):
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var cms = new ContextMenuStrip();
cms.Items.Add("Show", null, ShowForm);
cms.Items.Add("Exit", null, ExitProgram);
var ni = new NotifyIcon();
ni.Icon = Properties.Resources.SampleIcon;
ni.ContextMenuStrip = cms;
ni.Visible = true;
Application.Run();
ni.Dispose();
}
private static void ShowForm(object sender, EventArgs e) {
// Ensure the window acts like a singleton
if (MainWindow == null) {
MainWindow = new Form1();
MainWindow.FormClosed += delegate { MainWindow = null; };
MainWindow.Show();
}
else {
MainWindow.WindowState = FormWindowState.Normal;
MainWindow.BringToFront();
}
}
private static void ExitProgram(object sender, EventArgs e) {
Application.ExitThread();
}
private static Form MainWindow;
}

Splash Screen that doesn't go away

I'm using a Splash Screen from Here. I love how simple it is. But the problem with it is that the splash screen doesn't go away until I click on it. When run within the IDE it works fine. Any ideas? I'd attach the code here but its not inserting properly for some reason.
private System.Windows.Forms.Timer timer1;
//private Splash sp=null;
public Form1()
{
InitializeComponent();
Thread th = new Thread(new ThreadStart(DoSplash));
//th.ApartmentState = ApartmentState.STA;
//th.IsBackground=true;
th.Start();
Thread.Sleep(3000);
th.Abort();
Thread.Sleep(1000);
}
private void DoSplash()
{
Splash sp = new Splash();
sp.ShowDialog();
}
private void timer1_Tick(object sender, System.EventArgs e)
{
// sp.Close();
}
First of all, the way the splash screen on that page is done, using Thread.Abort, is not the right way to do things.
Never call Thread.Abort, unless you're in the process of shutting down the AppDomain the thread lives in.
Let me reiterate that for emphasis. The only time you should call Thread.Abort is when you know enough about Thread.Abort and how it behaves to know that you should never call it.
Take a look at this other question on StackOverflow: Multi-Threaded splash screen in c#?.
If you want to keep your existing solution, a possible better way would be to drop a timer into the splash screen form, set its timer to the time you want the splash screen to stay on screen, and call Close in its Tick event handler.
In the same venue, I would simply fire off that original thread, and remove the other lines.
In other words, from the first code block on that page, I would keep these two lines:
Thread th = new Thread(new ThreadStart(DoSplash));
th.Start();
Couple that with that timer on the form that makes the form self-closing, and you're in way better shape than trying to get to grips with Thread.Abort.
Which you should not call.
The DoSplash should probably call sp.Show() instead of sp.ShowDialog()
But calling Sleep(x000) form your main thread to show a splash screen isn't very efficient, and Thread.Abort() should be reserved for better use too.
A better approach is to close your SplashForm with a Timer and set a minimum delay. On startup you can Show() and Update() the SplahForm and continue to load stuff. The timer event won't fire until the MainForm is finished initializing and enters the EventLoop. No threads needed, and the MainForm doesn't have to be involved either.
Just to give you the main ingredients of a MessageLoop driven SplashForm:
public partial class SplashForm : Form
{
// default stuff
public static void Splash()
{
var s = new SplashForm();
s.Show();
s.Update();// force paint
}
private void SplashForm_Load(object sender, EventArgs e)
{
Timer t = new Timer();
t.Interval = 1; // wait for EventLoop
t.Tick += GoAway;
t.Enabled = true;
}
private void GoAway(object sender, EventArgs e)
{
this.Close();
}
}
and then, in Program.cs,
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashForm.Splash();
Application.Run(new MainForm());
That's an ugly implementation. You should check out this SO thread. That describes how to use the VisualBasic.NET namespace from C# and the OnCreateSplashScreen method to do a much cleaner splash screen implementation.

Categories