Following this SO answer, I'm doing:
ThreadPool.QueueUserWorkItem(
delegate
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
});
My goal is to do a garbage collection run after I close a large WinForms form with lots of images/PictureBox controls to ensure I have no images in memory anymore. (I do believe I follow the instructions of Jon Skeet).
I'm doing it in a background thread in order to try to have my UI responsive.
My question:
Does it bring me any benefits to do the garbage collection in a background thread? Or does it actually make my application slower/hang longer?
The garbage collector is not thread-safe. If your application is multi-threaded, each thread will (automatically) have its own garbage collection context.
The general advice is that you should not call GC. Collect from your code, but what are the exceptions to this rule? I can only think of a few very specific cases where it may make sense to force a garbage collection.
You can force garbage collection either to all the three generations or to a specific generation using the GC. Collect() method. The GC. Collect() method is overloaded -- you can call it without any parameters or even by passing the generation number you would like to the garbage collector to collect.
4) Garbage Collection in Java is carried by a daemon thread called Garbage Collector. 5) Before removing an object from memory garbage collection thread invokes finalize() method of that object and gives an opportunity to perform any sort of cleanup required.
You are throwing away the option to have garbage collection performed on the background when you do this. Or in other words, your UI thread is going to get suspended anyway, regardless if you do this from a worker thread. The only possible way to be ahead is when GC.WaitForPendingFinalizers() is taking a substantial amount of time. It is not actually something you should ever be waiting for, there is no point, and if it takes more than the blink of an eye then you are hiding pretty serious bugs in your code.
Another significant wrinkle is that the workstation version of Windows gives any thread that owns the foreground window a larger quantum. In other words, it is allowed to burn core longer than a background thread. A simple hack to make Windows more responsive to the user.
Too many moving parts, it is really rather best to test your theory so you can be sure that running a collection on a worker is actually something you are ahead with. Measuring UI thread suspensions is pretty simple, you can use a Timer to do this. Its Tick event cannot run when the thread is suspended. Start a new Winforms project, drop a Timer on the form, set its Interval to 1 and Enabled to True, add a Label and use this code to measure delays:
int prevtick = 0;
int maxtick = -1;
private void timer1_Tick(object sender, EventArgs e) {
int tick = Environment.TickCount;
if (prevtick > 0) {
int thistick = tick - prevtick;
if (thistick > maxtick) {
maxtick = thistick;
label1.Text = maxtick.ToString();
}
}
prevtick = tick;
}
Run your program, you should be seeing 16 in the label. If you get less then you ought to get your machine fixed, not otherwise anything that affects this test. Add a button to reset the measurement:
private void button1_Click(object sender, EventArgs e) {
maxtick = -1;
}
Add a checkbox and another button. We'll have it perform the actual collection:
private void button2_Click(object sender, EventArgs e) {
var useworker = checkBox1.Checked;
System.Threading.ThreadPool.QueueUserWorkItem((_) => {
var lst = new List<object>();
for (int ix = 0; ix < 500 * 1024 * 1024 / (IntPtr.Size * 3); ++ix) {
lst.Add(new object());
}
lst.Clear();
if (useworker) {
GC.Collect();
GC.WaitForPendingFinalizers();
}
else {
this.BeginInvoke(new Action(() => {
GC.Collect();
GC.WaitForPendingFinalizers();
}));
}
});
}
Play with this, hit button2 to start the collection and pay attention to the value in the Label. Turn on the checkbox so it runs on the worker and compare. Use button1 to reset the maximum in between. And modify the allocation code, you probably want to do something with bitmaps, whatever you do to require this hack.
What I see: ~220 msec delay when performing the collection on the UI thread, ~340 msec delay when running on the worker. Clearly, this is not an improvement at all. From where I sit, your theory is dead in the water. Please try this yourself, I've got only one datapoint. Do beware that it is going to look very different on a server version of Windows or with <gcServer=true>
in the .config file. Something else you can play with.
Update: The reasoning in this answer seems pretty sound but the answer by Hans Passant below shows that the conclusion does not hold. Don't jump to conclusions based on this answer.
This is a good idea. All CLR GC algorithms pause each thread at least once but the pauses are smaller than the total GC time. The call to GC.Collect
takes as long as the total GC time takes. It has the maximum latency possible for any GC cycle. That's why it is a good idea to not call it on the UI thread.
Your UI thread will be paused during the GC at least once but not for the whole duration. It depends on the CLR version and GC settings how long and how many pauses there will be.
Summary: This reduces UI pause time but does not entirely avoid it. I recommend doing this since there is no harm being done.
Alternatively, dispose all unmanaged resources. This question seems to a assume a situation where that is not possible or too onerous.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With