Caching values at startup with a thread-safe lazy load

Often I want to cache some shared values in a class at application startup time. Now I use the simple, thread-safe “lazy load” approach described here.

A while back, I read a post by Mike Woodring that really helped me understand locking better. Mike’s post helped me catch a subtle bug I had introduced because I was trying to be a bit too fancy. I wanted to be able to reinitialize values at runtime, but I wasn’t performing my read locks correctly (a bit more on that below).

Ultimately, reinitializing values wasn’t in my requirements, so I pared down my approach to the technique shown below.

' sample vb.net class
Public Class MyCache
    Private Const _TimeOut As Integer = 200 ' in Milliseconds
    Private Shared _Initialized As Boolean
    Private Shared _Lock As New Object
    Private Shared _Value As String

    Private Shared Sub Initialize()
        Do While Not _Initialized
            If System.Threading.Monitor.TryEnter(_Lock, _TimeOut) Then
                Try
                    If Not _Initialized Then
                         ' One and only place to set cached values
                        _Value = "Hey there"

                         ' One and only place to set initialized flag
                        _Initialized = True
                    End If
                Finally
                    System.Threading.Monitor.Exit(_Lock)
                End Try
            Else
                ' Perhaps increment a local counter and 
                ' throw exception after a certain # of retries
            End If
        Loop
    End Sub

    Public Shared ReadOnly Property Value()
        Get
            Initialize()
            Return _Value
        End Get
    End Property
End Class

First of all, _Initialized starts life as false. Whenever it’s true, then we can assume MyCache has been initialized by somebody (maybe me, maybe some other thread). Thanks to Monitor.TryEnter, only one thread gets to run the actual initialization code (in the Try block). That code does the actual initialization, which presumably is relatively slow (otherwise, why would you be doing all this in the first place), like getting values from a database or a configuration file of some sort.

Putting the Monitor.Exit in a Finally block ensures that it gets called even if the initialization code barfs. The loop is probably not necessary - it has the same effect as setting an infinite timeout in the TryEnter. However, by using an explicit timeout, you could retry a number of times and eventually throw an exception.

The advantage to this approach is its overall simplicity. It’s very easy to call the initialization code and the caller of the class is blissfully unaware of the concurrency checking. It’s also easy to implement this initialization pattern for any class that needs to configure shared properties at startup time.

As I mentioned before, it’s a bit more difficult to handle the case where the values can be reinitialized sometime after startup. For that, you’d need some sort of reader/writer lock to block reads when a reinitialize has been signaled. There is a minor performance cost to adding read locks - there’s no read locking at all right now, only write locking, so once the object is initialized, performance will be just like any other lazy load property – read locking will be somewhat slower. A bigger deal to me is simply that read locking makes the solution harder to understand.

The technique shown here works great for initializing shared properties from a database in asp.net. If you need to change values, then touch the web.config file. This will cause the asp.net web application to restart and shared properties will be reinitialized. Touching web.config (or restarting iis) might not be appropriate for every situation, but it works just fine when the lazy load values change very rarely.

Leave a Reply

You must be logged in to post a comment.