0

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!

Brian MacKay
  • 31,133
  • 17
  • 86
  • 125

1 Answers1

1

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:

'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

' My thread is right here, when you visit the site

Return MultiSyringeCache(orderLabelID)

and then your thread comes in here and supercedes my thread:

Set(value As Boolean)
    ClearMultiSyringeCache()
    m_MultiSyringeCacheEnabled = value
End Set

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.

Community
  • 1
  • 1
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • Chris, I thought that since the web is stateless, these shared variables would reset on each postback. Is that not how it works?? – Brian MacKay Jun 14 '12 at 16:37
  • (also, I was thinking that each request had its own copy of these shared variables). – Brian MacKay Jun 14 '12 at 16:39
  • No, that's not how it works. All web requests are in their own *thread*, but they mostly share the same process, which means that shared variables are shared among requests. – Chris Shain Jun 14 '12 at 16:53