Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is SyncLock not working here?

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?

like image 822
Luc VdV Avatar asked Dec 25 '22 21:12

Luc VdV


2 Answers

    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.

like image 89
Hans Passant Avatar answered Jan 06 '23 01:01

Hans Passant


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
like image 44
Magnus Avatar answered Jan 05 '23 23:01

Magnus