BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Algorithms and Data Structures
.NET 4.5+

A Lazily Loaded Weakly Referenced Cache

Caching improves performance for data that is slow to obtain. Lazy initialisation can help performance and save resources for unused objects. Weak references allow early garbage collection. This article describes a cache class combining these concepts.

Adding the Object Property

The final part of the class is the Object property. This is a read-only property that returns a strong reference to the cached object. The property first checks if the _cache field is null. If it is, the weak reference needs to be instantiated. If the object is not null, the TryGetTarget method call attempts to create the strong reference. If this succeeds, the new reference is returned. If not, the weak reference needs to be re-initialised.

Within the code block of the if statement we execute the generation function to create a new object. This is stored in the _cache field, wrapped in a WeakReference<T>, and returned.

The property's code is contained within a lock statement. This ensures that only one thread may enter the key elements of the code at any time. If two threads were permitted to execute the method it would be possible for both to execute the initialisation function and return different strong references. This could lead to unpredictable behaviour.

Add the property as shown below:

public T Object
{
    get
    {
        lock (_lock)
        {
            T obj;
            if (_cache == null || !_cache.TryGetTarget(out obj))
            {
                obj = _generateFunction();
                _cache = new WeakReference<T>(obj);
            }
            return obj;
        }
    }
}

Testing the LazyCache<T>

To test the new class with a simple example we'll use a function that slowly generates an array of ten integers. We'll add an artificial delay to the process using the Sleep method of the Thread class. This is in the System.Threading namespace so ensure that you include the following using directive:

using System.Threading;

The example code for the Main method and two supporting methods is displayed below. GenerateIntegerArray creates and returns the integer array that we will cache. This takes approximately ten seconds to run due to the Sleep calls. To show progress the method outputs characters to the console. The ShowIntegerArray method shows the contents of an integer array by outputting the numbers to the console.

The Main method initialises a LazyCache object with a type parameter that specifies that it holds an integer array. The function provided calls the GenerateIntegerArray method so when we first access the cached object, or when we read it after garbage collection, we'll see the ten-second delay as the array is built.

Try running the code. You should see from the messages that the cached object is only created once.

static void Main(string[] args)
{
    LazyCache<int[]> values = new LazyCache<int[]>(GenerateIntegerArray);

    Console.WriteLine("Cache Instantiated");

    ShowIntegerArray(values.Object);
    ShowIntegerArray(values.Object);
}

static int[] GenerateIntegerArray()
{
    Console.Write("Generating");

    int[] array = new int[10];
    for (int i = 0; i < 10; i++)
    {
        array[i] = i * i;
        Thread.Sleep(1000);
        Console.Write(".");
    }
    Console.WriteLine();
    return array;
}

static void ShowIntegerArray(int[] array)
{
    Console.Write("Array > ");
    for (int i = 0; i < array.Length; i++)
    {
        Console.Write(" {0}", array[i]);
    }
    Console.WriteLine();
}

/* OUTPUT

Cache Instantiated
Generating..........
Array >  0 1 4 9 16 25 36 49 64 81
Array >  0 1 4 9 16 25 36 49 64 81

*/

To show that the cache can rebuild itself after the weak reference is garbage collected, modify the contents of the Main method as shown below. The new version outputs the array twice before starting garbage collection. On the third access you should see the "Generating" message and the associated delay as the array is reconstructed.

LazyCache<int[]> values = new LazyCache<int[]>(GenerateIntegerArray);

Console.WriteLine("Cache Instantiated");

ShowIntegerArray(values.Object);
ShowIntegerArray(values.Object);

GC.Collect();

ShowIntegerArray(values.Object);

/* OUTPUT

Cache Instantiated
Generating..........
Array >  0 1 4 9 16 25 36 49 64 81
Array >  0 1 4 9 16 25 36 49 64 81
Generating..........
Array >  0 1 4 9 16 25 36 49 64 81

*/
28 December 2012