Weak References

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 size, but might not be actively in use for some time. Let's say for example that you have an application connected to a database on a remote server. This application allows the user to find out geographical information, and some user has used it to download a list (probably via a DataSet) of all the towns and villages in the United Kingdom, complete with information about each town - population, any buildings of interest to tourists, etc. The user browsed the list for a few seconds, then decided he was particularly interested in Lancaster, and so asked the application to download a map of that area from the database. What is going to happen to the list of towns while the user is busily exploring the map, or perhaps asking for more maps such as a higher scale one of the area? Now that the application has downloaded the list, it would be quite nice to keep it locally, ready for when the user wants to come back to it. You certainly don't want to work on the basis of displaying a tiny bit of information from the list and then promptly deleting the whole list - that's going to give very poor responsiveness if the user follows up one query with another one concerning the same list, since such a long list will probably take a few seconds to download again from the database. On the other hand, that list is going to be taking up a lot of memory. If the application simply caches everything the user asks for, then by the time we've gone through the list and several maps, you could easily imagine performance decreasing drastically because the application is hogging too much memory and needs to keep paging.

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 initialized to refer to our data. This means that the data will stay in memory, but it is also marked as available for garbage collection. So, if the garbage collector does get called in, this data will be freed up. The WeakReference class exposes a property, Target, which lets us see if the data has been collected. If Target returns null, the object referred to by the weak reference has been collected, and we need to create another copy of it to be able to use it again. Otherwise, the data is still there. Notice that this means that weak references are only a suitable solution if you can, in principle, recreate the original data without too much trouble.

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 remain after you have finished actively using it. As long as any such references exist, the garbage collector cannot collect the data, which rather nullifies the whole purpose of having the weak reference in the first place!

And that's literally all there is to it. You've gained a small bit of complexity in your code, but in return you get the peace of mind of knowing that your application will be using memory in a much more intelligent way. (And let's face it, trying to implement something like a weak reference by hand would be a huge task - especially since the GC doesn't expose any means to inform your code when it is under memory pressure.)

Weak References Sample

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:

click to expand

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-intensive objects being allocated resulting in a garbage collection. Clicking this button forces a garbage collection using the GC.Collect() method. This means that if the user clicks Force Collection, then clicks Show Data, the data will have to be recreated before it can be displayed. This means that the created time will get updated. This isn't quite in the spirit of weak references - the real idea is that your supposed to recreate the data exactly as it was before, but having an updated time is more illustrative for the sample.

To create the sample, I generated a standard C# Windows Forms application and added the buttons and list box to the form. The controls were respectively named btnShowData, btnForceCollection, and lbData. Then I manually added this code: first a couple of member variables to the Form1 class, as well as constants representing the length of the array and the number of elements of it that will actually get displayed in the list box (I confined it to the first 20 elements since for obvious reasons I didn't really want to have to populate and display a list box with half a million items in it!)

   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 replaces its contents with a single entry indicating to the user that the listbox will be populated soon. It also changes the mouse cursor to an hourglass wait cursor in accordance with normal UI practice. Then it declares a local object that will hold a strong reference to the array of strings - so we can manipulate the array locally in this method. We check to see if the weak reference currently refers to the array, and if it doesn't, we create it. The if statement tests two conditions:

    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 singly.

The best way to see the sample in action is to watch memory usage with Task Manager while running it:

click to expand

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 hitting Force Collection, I clicked on Refresh Data a few times. Although this did refresh the list box (I could tell as I saw it flicker), refreshing the data this time had no effect on memory or CPU usage, since the array of strings was already in memory. Then I hit Force Collection again, and you can see the memory usage dropping back down. There's also a small drop in memory usage later on as I actually exit the program.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net