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?
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
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With