Most people's multi-threading needs are fairly modest so most people don't stray from the usual: the BackgroundWorker or Thread class and maybe the ThreadPool class and the odd SyncLock statement. The System.Threading namespace contains various other classes to aid in thread synchronisation though. Two that will help in your case are the Interlocked class and the ReaderWriterLockSlim class.
The Interlocked class provides methods that allow you to perform simple operations and guarantee that they will be atomic. In your case, you will have a flag that indicates whether the cache is being refreshed. Using the Interlocked class, you can test the value of that flag and set it on one thread as an atomic operation, ensuring that another thread doesn't slip in between the test and the set and start refreshing the cache too. I'm not sure how you're expiring your cache but you might use something similar for that.
The ReaderWriterLockSlim class allows you to synchronise access to a shared resource such that multiple threads can read the resource simultaneously but only one thread can ever write to the resource at a time, and reading and writing can never occur simultaneously. In your case, the cache is the shared resource. You can have as many threads as you like reading data from the cache but, as soon as a thread gets a write lock to refresh the cache, no other threads can read until the write lock is released.
Your code might look something like this: Code:
'Zero for False and non-zero for True.
Private cacheNeedsRefreshing As Long
'Zero for False and non-zero for True.
Private cacheBeingRefreshed As Long
Private cache As SomeClass
Private lock As New ReaderWriterLockSlim
Private Function GetData() As String
'The first expression gets the value of cacheNeedsRefreshing as a Boolean in an atomic operation. It evaluates to True if the cache needs refreshing.
'The second expression tests whether cacheBeingRefreshed as a Boolean is False and, if so, sets it to True. It evaluates to True if the cache is not currently being refreshed.
If CBool(Interlocked.Read(Me.cacheNeedsRefreshing)) AndAlso
Not CBool(Interlocked.CompareExchange(Me.cacheBeingRefreshed,
CLng(True),
CLng(False))) Then
'Start a new thread to refresh the cache.
'We are guaranteed that this will only be done by one thread at a time.
Call New Thread(AddressOf RefreshCache).Start()
End If
Dim data As String = Nothing
'Get non-exclusive read access to the cache.
lock.EnterReadLock()
Try
'Get the data from the cache.
data = Me.cache.Data
Catch ex As Exception
Debug.WriteLine(ex.ToString())
Finally
'Release the lock on the cache.
lock.ExitReadLock()
End Try
Return data
End Function
Private Sub RefreshCache()
'Get exclusive write access to the cache.
lock.EnterWriteLock()
Try
'Refresh the cache.
cache.Data = GetNewData()
Catch ex As Exception
Debug.WriteLine(ex.ToString())
Finally
'Release the lock on the cache.
lock.ExitWriteLock()
'Reset the flag that indicates the cache needs to be refreshed.
Interlocked.Exchange(Me.cacheNeedsRefreshing, CLng(False))
'Reset the flag that indicates the cache is being refreshed.
Interlocked.Exchange(Me.cacheBeingRefreshed, CLng(False))
End Try
End Sub
Bookmarks