I was trying to do a little data access optimization, and I ran into a situation where a dictionary appeared to get out of sync in a way that should be impossible, unless I’m somehow getting into a multithreaded situation without knowing it.
One column of GridLabels binds to a property that does data access — which is a tad expensive. However, multiple rows end up making the same call, so I should be able to head any problems off at the pass by doing a little caching.
However, elsewhere in the app, this same code is called in ways where caching would not be appropriate, I needed a way to enable caching on demand. So my databinding code looks like this:
OrderLabelAPI.MultiSyringeCacheEnabled = True
Me.GridLabels.DataBind()
OrderLabelAPI.MultiSyringeCacheEnabled = False
And the expensive call where the caching happens looks like this:
Private Shared MultiSyringeCache As New Dictionary(Of Integer, Boolean)
Private Shared m_MultiSyringeCacheEnabled As Boolean = False
Public Shared Function IsMultiSyringe(orderLabelID As Integer) As Boolean
If m_MultiSyringeCacheEnabled Then
'Since this can get hit a lot, we cache the values into a dictionary. Obviously,
'it goes away after each request. And the cache is disabled by default.
If Not MultiSyringeCache.ContainsKey(orderLabelID) Then
MultiSyringeCache.Add(orderLabelID, DoIsMultiSyringe(orderLabelID))
End If
Return MultiSyringeCache(orderLabelID)
Else
Return DoIsMultiSyringe(orderLabelID)
End If
End Function
And here is the MultiSyringeCacheEnabled property:
Public Shared Property MultiSyringeCacheEnabled As Boolean
Get
Return m_MultiSyringeCacheEnabled
End Get
Set(value As Boolean)
ClearMultiSyringeCache()
m_MultiSyringeCacheEnabled = value
End Set
End Property
Very, very rarely (unreproducably rare…) I will get the following exception: The given key was not present in the dictionary.
If you look closely at the caching code, that’s impossible since the first thing it does is ensure that the key exists. If DoIsMultiSyringe tampered with the dictionary (either explicitly or by setting MultiSyringeCacheEnabled), that could also cause problems, and for awhile I assumed this had to be the culprit. But it isn’t. I’ve been over the code very carefully several times. I would post it here but it gets into a deeper object graph than would be appropriate.
So. My question is, does datagridview databinding actually get into some kind of zany multithreaded situation that is causing the dictionary to seize? Am I missing some aspect of shared members?
I’ve actually gone ahead and yanked this code from the project, but I want to understand what I’m missing. Thanks!
Since this is ASP.NET, you have an implicit multithreaded scenario. You are using a shared variable (see What is the use of a shared variable in VB.NET?), which is (as the keyword implies) “shared” across multiple threads (from different people visiting the site).
You can very easily have a scenario where one visitor’s thread gets to here:
and then your thread comes in here and supercedes my thread:
Then my thread is going to try to read a value from the dictionary after you’ve cleared it.
That said, I am not sure what performance benefit you expect from a “cache” that you clear with every request. It looks like you should simply not make this variable shared- make it an instance variable- and any user request accessing it will have their own copy.