Friday 13 November 2015

Thread Synchronization with Semaphore and how to Implement it in C# & VB

In the previous couple of posts, I have posted about aquiring exclusive lock with SpinLock http://jaryl-lan.blogspot.com/2015/08/exclusive-lock-with-spinlock-c-vb.html or with Mutex http://jaryl-lan.blogspot.com/2015/08/thread-synchronization-with-mutex.html. So for today, I'm going to post about something different yet it can still provide exclusive right to a thread in a multi-threaded environment, which is Semaphore.

Semaphore is used to control the number of concurrent threads to access a particular section of the code / resources. With the ability to control the number of concurrent threads, It can do more than just acquiring exclusive lock by limiting to 1 thread. I will demonstrate on how to use Semaphore to control the concurrent threads with an example. The example below is by no means the best practice, it is just to show how Semaphore works.

Imagine that you need to process more than 1 file at the same time with FileSystemWatcher. By default, the event only process 1 file at a time. So to process multiple file at the same time and control the threads, we will use Task.Run to simulate fire-and-forget and Semaphore to control the threads.

As usual, you need an instance of Semaphore. The following constructor accept 2 parameters. The first parameter define the number of free / available requests can be accepted. The second parameter define the maximum number of concurrent threads. In this case, we have 3 available requests and can accept only up to 3 concurrent threads.

[C#]
Semaphore semaphore = new Semaphore(3, 3);

[VB]
Dim semaphore As Semaphore = New Semaphore(3, 3)


In your FileSystemWatcher's created event, call WaitOne() to occupy 1 available request. If the request is not available, it will wait until a new request is available.

[C#]
semaphore.WaitOne();

[VB]
semaphore.WaitOne()


We will use Task.Run to execute the method in another thread. In that method, call Release() to release the request. Allowing another thread to acquire the available request.

[C#]
semaphore.Release();

[VB]
semaphore.Release()



If you play around with the sample code, you will notice that the thread id is different from when it call WaitOne() and call Release(). This is due to the thread that execute Task.Run spawn a new thread and since it did not call await or .Wait() for the Task.Run, it end up leaving the Task.Run without waiting for its completion and exit the method. To re-explain this in steps, 
  1. Thread A call WaitOne() and execute Task.Run.
  2. Task.Run spawn a new thread B to further execute the method specified in Task.Run. 
  3. Thread A leaves without waiting thread B completes its execution and exit the method. (fire-and-forget)
  4. Thread B executing the method and call Release().




No comments:

Post a Comment