views:

765

answers:

6

Is this code correct or is there any possibility of some random threading deadlocks etc?

Is it a good idea to use static properties and locking together? Or is static property thread-safe anyway?

Private Shared _CompiledRegExes As List(Of Regex)
Private Shared Regexes() As String = {"test1.Regex", "test2.Regex"}
Private Shared RegExSetupLock As New Object

Private Shared ReadOnly Property CompiledRegExes() As List(Of Regex)
    Get
        If _CompiledRegExes Is Nothing Then
        SyncLock RegExSetupLock

            If _CompiledRegExes Is Nothing Then
                _CompiledRegExes = New List(Of Regex)(Regexes.Length - 1)

                For Each exp As String In Parser.Regexes
                    _CompiledRegExes.Add(New Regex(exp, RegexOptions.Compiled Or RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase))
                Next

            End If

        End SyncLock

    End If

    Return _CompiledRegExes

End Get
End Property

If it's not obvious code is compiling regexes and storing in a List(Of Regex) so they can run faster. And it's shared so every instance of class can get benefit out of it.

A: 

Deadlocks don't happen when you only have a single resource.

Regarding static properties, no they are not thread safe automatically. It's a common approach to lock on a private static field.

Mehrdad Afshari
+7  A: 

Your code isn't thread-safe because you're using double-checked locking without making the field volatile. Unfortunately VB.NET doesn't have a volatile modifier, so you can't apply the normal fix. Just acquire the lock every time instead, or use static initialization to initialize _CompiledRegExes when the type is initialized.

See my singleton page for a general discussion of this with regards to singletons - I know this isn't quite a singleton, but it's close. The page gives C# code, but it should be fairly easy to understand.

Additionally, I generally recommend making lock variables read-only. You really don't want to be changing the value :)

In general terms:

  • No, static properties aren't thread-safe automatically
  • Yes, it's okay to lock on a static variable (but initialize it explicitly, like you are doing)
  • Don't try to write lock-free or low-lock code unless you really know what you're doing. I regard myself as reasonably knowledgeable about threads, and I still don't try using double-checked locking etc.
  • Type initialization is thread-safe (with a few caveats if you have complex initializers which end up referencing each other) so that's a good time to do initialization like this - then you really don't need a lock.

EDIT: You don't need to make the type a singleton. Just write function to initialize the list and return it, then use that function in the initializer for the variable:

' This has to be declared *before* _CompiledRegExes '
' as the initializer will execute in textual order '
' Alternatively, just create the array inside BuildRegExes '
' and don't have it as a field at all. Unless you need the array '
' elsewhere, that would be a better idea. '
Private Shared ReadOnly Regexes() As String = {"test1.Regex", "test2.Regex"}

Private Shared ReadOnly _CompiledRegExes As List(Of Regex) = BuildRegExes()

Private Shared ReadOnly Property CompiledRegExes() As List(Of Regex)
    Get
        Return _CompiledRegExes
    End Get
End Property

Private Shared Function BuildRegExes() As List(Of Regex)
    Dim list = New List(Of Regex)(Regexes.Length - 1)

    For Each exp As String In Regexes
        _CompiledRegExes.Add(New Regex(exp, RegexOptions.Compiled Or RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase))
    Next
    Return list
End Function
Jon Skeet
there is no volatile keyword in VB.NET, Thanks for the article and now I know what you mean. Initializing the _CompiledRegExes in the start not a bad idea however I'm now quite sure where to do it. How can I fill the list values unless it's a singleton?
dr. evil
Downvoters: Please explain!
Jon Skeet
@Jon "Do empty lists count as "nothing" in VB?" - It's created without a "New" operator so it'll return nothing.
dr. evil
@Slough: You don't need it to be a singleton, just call a method to initialize the variable. I'll edit my answer...
Jon Skeet
@Jon Funny enough I didn't know that I can call shared method in there :) Learning a new thing everyday in SO :) Thanks
dr. evil
That seems a lot to do about nothing. There are perfectly acceptable lock-free methods such as using a type initializer. Your article adds a whole lot of complexity for a dubious performance gain.
Jonathan Allen
Your answer is definitely a good one and doesn't deserve a downvote by any means. +1 to compensate.
Mehrdad Afshari
@Grauenwolf: Did you read where I repeatedly suggested using a type initializer? There are times when lazy initialization is worthwhile.
Jon Skeet
... which is why I recommended reading my article. What's wrong with understanding a) why double-checked locking is a bad idea; b) why you might want to use lazy initialization for the property with a lock each time; c) why type initialization is generally a better option.
Jon Skeet
+2  A: 

(EDIT by jonskeet) This text is just to get the formatting right for the start of the class. No idea why it fails if there's no text before the code.)

Class Better
    Private Shared Regexes() As String = {"test1.Regex", "test2.Regex"}
    Private Shared _CompiledRegExes As List(Of Regex) = BuildList(Regexes)

    Private Shared Function BuildList(ByVal source() As String) As List(Of Regex)
        Dim result = New List(Of Regex)(Regexes.Length - 1)
        For Each exp As String In source
            result.Add(New Regex(exp, RegexOptions.Compiled Or RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase))
        Next
        Return result
    End Function
    Public Shared ReadOnly Property _CompiledRegExes1() As List(Of Regex)
        Get
            Return _CompiledRegExes
        End Get
    End Property

End Class

Class MoreBetter
    Private Shared ReadOnly Regexes() As String
    Private Shared ReadOnly _CompiledRegExes As List(Of Regex)

    Shared Sub New()
        Regexes = New String() {"test1.Regex", "test2.Regex"}
        _CompiledRegExes = New List(Of Regex)(Regexes.Length - 1)
        For Each exp As String In Regexes
            _CompiledRegExes.Add(New Regex(exp, RegexOptions.Compiled Or RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase))
        Next
    End Sub

    Public Shared ReadOnly Property _CompiledRegExes1() As List(Of Regex)
        Get
            Return _CompiledRegExes
        End Get
    End Property

End Class

EDIT: The reason I like the second one is that it is less error prone. The first one will not work if "Private Shared _CompiledRegExes" is accidentally moved before "Private Shared Regexes()"

Jonathan Allen
How shared Sub New() behaviour is? Doesn't it gonna execute it every time I create a new instance?
dr. evil
No, Shared Sub New happens only once per AppDomain. It was designed specifically for what you are trying to acomplish.
Jonathan Allen
@Slough: No, that's equivalent to a static constructor in C#. It happens once, at type initialization time. However, you don't need it - you can just initialize from the static variable declaration.
Jon Skeet
I see, it's good stuff. I can use either way. Thanks for the tip.
dr. evil
+1  A: 

VB.Net has a Static keyword you can use to declare variables in members or properties. Variables declared this way retain their state across calls to the method and are guaranteed to be threadsafe (implemented with the Monitor class behind the scenes).

Joel Coehoorn
How would you change this code with static? I'm not quite sure how to do it.
dr. evil
Correctly using Static in VB is hard, but I will post an example.
Jonathan Allen
Thanks, would be nice to see a nice example of it for future reference. +1
dr. evil
See Grauenwolf's post for the example.
Joel Coehoorn
+1  A: 

Though I rarely support using this method, it is legitmate.

Class UsingStatic
Private Shared Regexes() As String = {"test1.Regex", "test2.Regex"}
Public Shared ReadOnly Property CompiledRegExes() As List(Of Regex)
    Get
        Static _CompiledRegExes As List(Of Regex) = BuildList(Regexes) <--executes once
        Return _CompiledRegExes <-- executes every time it is called
    End Get
End Property

Private Shared Function BuildList(ByVal source() As String) As List(Of Regex)
    Dim result = New List(Of Regex)(Regexes.Length - 1)
    For Each exp As String In source
        result.Add(New Regex(exp, RegexOptions.Compiled Or RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase))
    Next
    Return result
End Function

End Class

Note that you MUST put the constructor for _CompiledRegExes on the same line. This tiny change will cause you to get a new object each time.

Public Shared ReadOnly Property CompiledRegExes() As List(Of Regex)
    Get
        Static _CompiledRegExes As List(Of Regex) <--executes once
        _CompiledRegExes = BuildList(Regexes) <-- executes every time it is called
        Return _CompiledRegExes
    End Get
End Property
Jonathan Allen
A: 

from msdn "The lockobject expression should always evaluate to an object that belongs exclusively to your class. You should declare a Private object variable to protect data belonging to the current instance, or a Private Shared object variable to protect data common to all instances."

dbasnett