|
|
||
|
|
||
We will finish this chapter off by examining one last feature that the garbage collector makes available: weak references. A weak reference amounts to a way of holding on to a reference to an object, but at the same time saying to the garbage collector that if memory gets tight, it's okay to collect that object because you can always re-create it if you need it again.
| Important |
If you are familiar with the weak references in COM, you will need to forget everything you know about COM weak references. Although there are some similarities in the implementation, the purpose of .NET weak references is completely different. In COM, weak references were a rather complicated means to avoid the circular reference problem. Weak references in .NET are simpler and exist purely in order to give the garbage collector extra freedom to clean up objects if memory is limited. |
The circumstances when you will consider using a weak reference are if you have some object that is massive in
It looks like we are in a no-win situation, but enter weak references to save the day. Once we've downloaded the data, instead of storing a reference object that contains the data, we store a
weak reference
referring to the data - that is to say, an instance of the class
System.WeakReference
that is
In contrast to weak references, ordinary .NET references are often known as strong references. Bear in mind that a weak reference to an object can only function correctly as long as there are no strong references to the same object. If one strong reference to the object exists, then the object will be completely prevented from being garbage collected.
Let's look at how this is implemented in coding terms. Suppose we have a method, GetTheData() , which calculates some data and returns a reference of type MyData to this data. Ordinarily, you'd have code like this:
MyData data = GetTheData();
Followed later on by some method that uses the data:
UseTheData(data);
Instead, using a weak reference, you'd do this when first creating the data:
WeakReference wr = new WeakReference(GetTheData());
Note that the constructor to WeakReference takes an object reference - so you can pass in any reference type to it.
Then when you want to use the data, you'll do something like this:
MyData data; if (wr.Target.== null) { // The data got deleted. Need to recreate it. data = GetTheData(); wr.Target = data; } else data = (MyData)wr.Target; UseTheData(data); data = null;
Notice the fact that the data variable has been set to
null
at the end of this block of code. As I mentioned earlier, it's important that no references to the actual data
And that's literally all there is to it. You've
We'll finish off with a sample called WeakReferenceDemo that illustrates the use of weak references. To keep things simple, we're not going to be accessing a database. Instead, the sample will have an array of half a million strings hogging memory. Here's what the sample looks like when it's running:
The hub of the sample is a button labeled Show Data , and a list box. Whenever the user clicks on the Show Data button, the list box is refreshed with the first 20 strings from the five-hundred-thousand-strong array. Each string, as you can see, is initialized to some text that indicates its index in the array and the time of creation for the array.
The second button simulates the effects of lots of other memory-
To create the sample, I generated a standard C# Windows Forms application and added the
public class Form1 : System.Windows.Forms.Form {
private const int DataArrayLength = 1000000;
private const int ItemsInListBox = 20;
private WeakReference wr;
private System.Windows.Forms.Button btnForceCollection; private System.Windows.Forms.Button btnShowData; private System.Windows.Forms.ListBox lbData;
Notice that I have stored the weak reference as a field, but I have been careful not to store any strong reference to the data in any field.
Now for the event handlers. On clicking the Show Data button, a method named RefreshData() gets called. We'll look at RefreshData() in a minute.
private void btnShowData_Click(object sender, System.EventArgs e) { RefreshData(); }
As described earlier, the other button forces a garbage collection:
private void btnForceCollection_Click(object sender, System.EventArgs e) { GC.Collect() ; }
Now for the RefreshData() method, which does all the work:
private void RefreshData() { Cursor.Current = Cursors.WaitCursor; lbData. Items. Clear() ; lbData.Items.Add("Retrieving data. Please wait ..."); lbData.Refresh() ; string[] dataArray; if (wr == null wr.Target == null) { dataArray = new string [DataArrayLength]; string text = " Created " + DateTime.Now.ToString("f"); for (int i=0 ; i<DataArrayLength ; i++) dataArray[i] = "Element " + i.ToString() + text; wr = new WeakReference(dataArray); } else dataArray = (string[] )wr.Target; string [] tempstrings = new String[ItemsInListBox]; for (int i=0 ; i<ItemsInListBox ; i++) tempstrings[i] = dataArray[i]; lbData.Items.Clear(); lbData. Items. AddRange (tempstrings) ; Cursor.Current = Cursors.Default; }
This method first clears out the list box, and
if (wr = = null wr.Target == null)
The wr = = null condition picks whether this is the first time RefreshData() has been called - in which case wr hasn't been initialized yet. The condition wr. Target == null will pick up the case where the array of strings has been created, but has since been garbage collected.
Notice that we create a temporary array and use the
ListItemCollection.AddRange()
method to populate the list box - that's just a bit more efficient than adding the items
The best way to see the sample in action is to watch memory usage with Task Manager while running it:
In the above display, I started up
WeakReferenceDemo
running with a release build (that's important -if I'm monitoring memory I don't want memory being cluttered up by debug symbols and so on). You can clearly see when I first click
Refresh Data
. The memory usage builds up to a new level - you can see it going up gradually as the array is populated, and the CPU Usage hits nearly 100% while that happens. A few seconds later, I hit
Force Collection
, and the memory usage dramatically fell back to its original level as the garbage collector releases its memory (it's clear from this screenshot that in this case the garbage collector actually decommits the memory). Then I hit
Refresh Data
again, and we see the same memory buildup. This time, before
|
|
||