4

I am working on a class library that will provide asynchronous communication to CLR applications.

There are asynchronous reads (BeginRead) on SslStream with a single callback routine shared by multiple streams. I don't want the callbacks to be processed in parallel during debugging, so I created a critical section:

Private Sub Callback_Read(ByVal ar As IAsyncResult)
   Static OneAtATime As New Object
   SyncLock OneAtATime
      Dim ThisSslStream As SslStream = DirectCast(ar.AsyncState, SslStream)
      ...
   End SyncLock
End Sub

To my surprise, that doesn't work, at least not when I set a breakpoint within the SyncLock block. Callbacks for multiple streams run within it at the same time, without waiting at the entry point until the previous thread has left it.

Single stepping it is a nightmare, especially when streams are being shut down (closed) simultaneously: execute line for stream 1, execute line for stream 2, execute next line for 1, execute next line for 2, and so on, through the entire block.

I thought maybe you need something more than just a generic "New Object", but then I saw there is at least one answer here on stack overflow that illustrates SyncLock exactly the way I am using it, with just "Static X as New Object" to create the synchronization object within the function that has to be locked.

Is it because the callback is actually coming from a win32 thread outside of the .Net framework that SyncLock doesn't work here?

Luc VdV
  • 1,088
  • 9
  • 14
  • Is the method always being called on the same object, or via separate objects of that type? – Steven Doggart Mar 26 '15 at 13:57
  • VB.NET doesn't use "static" but shared. Also try moving the locking object outside the method scope. – Allan S. Hansen Mar 26 '15 at 14:09
  • 1
    @AllanS.Hansen That's not correct. VB.NET does allow use of the `Static` modifier for local variables, just as VB6 did. – Steven Doggart Mar 26 '15 at 14:12
  • 1
    @AllanS.Hansen [`Static`](https://msdn.microsoft.com/en-us/library/z2cty7t8.aspx) in VB does exist, but has a different meaning to C# – James Thorpe Mar 26 '15 at 14:12
  • @JamesThorpe Define "normal". The meaning of it in this context is the same as it's meaning in the same context in C. – Steven Doggart Mar 26 '15 at 14:13
  • @StevenDoggart Yeah - realised that as soon as I hit enter... edited my comment – James Thorpe Mar 26 '15 at 14:14
  • For what it's worth, I think `Static` variables in VB.NET should probably be avoided in most cases. Usually a private field in the class is more appropriate. But it is valid. – Steven Doggart Mar 26 '15 at 14:15
  • The reason why I asked if you are calling the method on the same object is because `Static` variables are instance-variables. That means that each instance of the class will have a different value for that variable. As such, the `SyncLock` will only block re-entry for simultaneous method calls on the same object. – Steven Doggart Mar 26 '15 at 14:20
  • @Steven: via separate objects of the same type. Each object uses its own SslStream, but (at source code level) they all use the same callback function. – Luc VdV Mar 26 '15 at 14:36
  • If it's called on separate objects, then you need to use a `Shared` field. I'd write it up as an answer, but Magnus apparently already stole my thunder :) – Steven Doggart Mar 26 '15 at 14:37
  • @Steven: I kind of agree about avoiding Static variables, but I do see some cases where it has merit. Most of the time when I use them, if is to make sure they are accessible only from within the function where they are used, without being reset between function calls. I wish there was a way to make them shared and static at the same time, but the compiler doesn't allow it :) – Luc VdV Mar 26 '15 at 14:50
  • @LucVanderVeken Is there an equal thing in C# or this is VB only? – Magnus Mar 26 '15 at 15:02
  • @Magnus: I'm not 100% sure I understand what you mean, but in C or C++ you can have a 'static' (meaning shared for VB users) local variable in a function. My knowledge of C# is a bit less, but I think it's the same there. And that C sort of 'static' is what put me on the wrong foot here, thanks to you and steven for waking me up :) – Luc VdV Mar 26 '15 at 15:10

2 Answers2

8
    Static OneAtATime As New Object

The Static keyword was a fairly heavy millstone around the neck of the VB.NET implementers. They had to support it because it was frequently used in previous Visual Basic editions, omitting it would cause too much hardship on programmers that want to update their tooling.

But its legacy behavior was very incompatible with threading, a feature that's very strongly supported in .NET. Not an issue before because old VB editions didn't support creating threads. The amount of MSIL code generated for that statement is massive. You should take a look-see with the ildasm.exe utility.

It is massive because of what it needs to do. Which is initializing the variable only once, the first time the method is entered. Not terribly difficult, it auto-generates another Boolean variable that keeps track. But the more difficult part is to do it once for each individual thread. In other words, it has [ThreadStatic] behavior.

Which is what kills you here, every thread has its own SyncLock. Which is why you observed no synchronization at all :) You need to move it out of the method and declare it Shared.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

I have never seen the usage of static local variables in VB before. That such a thing existed was news to me. I would suggest that you do it the conventional way instead and use a shared class variable.

public Class Test
   Private shared SyncRoot As Object = new Object()

   Private Sub Callback_Read(ByVal ar As IAsyncResult)
      SyncLock SyncRoot 
         Dim ThisSslStream As SslStream = DirectCast(ar.AsyncState, SslStream)
         ...
      End SyncRoot
   End Sub

End Class
Magnus
  • 45,362
  • 8
  • 80
  • 118