Does SemaphoreSlim (.NET) provide the same stream that is included in the block?

I read the documents for SemaphoreSlim SemaphoreSlim MSDN which means that SemaphoreSlim will limit the code section that will only execute on 1 thread at a time, if you configure it as:

SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); 

However, it does not indicate whether the thread will stop the same access to this code. This is due to the asynx and is waiting. If the method uses wait, control leaves this method and returns when any task or thread is completed. In my example, I use a button with an async button handler. It calls another method (Function1) with 'await'. Function1 in turn calls

 await Task.Run(() => Function2(beginCounter)); 

Around my task. Run () I have a SemaphoreSlim. It seems to stop the same thread from accessing Function2. But this is not guaranteed (as I read it) from the documentation, and I wonder if this can be calculated.

I gave my complete example below.

Thanks,

Dave

  using System; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace AsynchAwaitExample { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); public MainWindow() { InitializeComponent(); } static int beginCounter = 0; static int endCounter = 0; /// <summary> /// Suggest hitting button 3 times in rapid succession /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void button_Click(object sender, RoutedEventArgs e) { beginCounter++; endCounter++; // Notice that if you click fast, you'll get all the beginCounters first, then the endCounters Console.WriteLine("beginCounter: " + beginCounter + " threadId: " + Thread.CurrentThread.ManagedThreadId); await Function1(beginCounter); Console.WriteLine("endCounter: " + endCounter + " threadId: " + Thread.CurrentThread.ManagedThreadId); } private async Task Function1(int beginCounter) { try { Console.WriteLine("about to grab lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter); await _semaphoreSlim.WaitAsync(); // get rid of _semaphoreSlim calls and you'll get into beginning of Function2 3 times before exiting Console.WriteLine("grabbed lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter); await Task.Run(() => Function2(beginCounter)); } finally { Console.WriteLine("about to release lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter); _semaphoreSlim.Release(); Console.WriteLine("released lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter); } } private void Function2(int beginCounter) { Console.WriteLine("Function2 start" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter); Thread.Sleep(1000); Console.WriteLine("Function2 end" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter); return; } } } 

Example output if you press the button 3 times. Note that Function2 always ends for a given counter before it restarts.

  beginCounter: 1 threadId: 9 about to grab lock threadId: 9 beginCounter: 1 grabbed lock threadId: 9 beginCounter: 1 Function2 start threadId: 13 beginCounter: 1 beginCounter: 2 threadId: 9 about to grab lock threadId: 9 beginCounter: 2 beginCounter: 3 threadId: 9 about to grab lock threadId: 9 beginCounter: 3 Function2 end threadId: 13 beginCounter: 1 about to release lock threadId: 9 beginCounter: 1 released lock threadId: 9 beginCounter: 1 grabbed lock threadId: 9 beginCounter: 2 Function2 start threadId: 13 beginCounter: 2 endCounter: 3 threadId: 9 Function2 end threadId: 13 beginCounter: 2 about to release lock threadId: 9 beginCounter: 2 released lock threadId: 9 beginCounter: 2 endCounter: 3 threadId: 9 grabbed lock threadId: 9 beginCounter: 3 Function2 start threadId: 13 beginCounter: 3 Function2 end threadId: 13 beginCounter: 3 about to release lock threadId: 9 beginCounter: 3 released lock threadId: 9 beginCounter: 3 endCounter: 3 threadId: 9 

If you get rid of SemaphoreSlim calls, you will get:

 beginCounter: 1 threadId: 10 about to grab lock threadId: 10 beginCounter: 1 grabbed lock threadId: 10 beginCounter: 1 Function2 start threadId: 13 beginCounter: 1 beginCounter: 2 threadId: 10 about to grab lock threadId: 10 beginCounter: 2 grabbed lock threadId: 10 beginCounter: 2 Function2 start threadId: 14 beginCounter: 2 beginCounter: 3 threadId: 10 about to grab lock threadId: 10 beginCounter: 3 grabbed lock threadId: 10 beginCounter: 3 Function2 start threadId: 15 beginCounter: 3 Function2 end threadId: 13 beginCounter: 1 about to release lock threadId: 10 beginCounter: 1 released lock threadId: 10 beginCounter: 1 endCounter: 3 threadId: 10 Function2 end threadId: 14 beginCounter: 2 about to release lock threadId: 10 beginCounter: 2 released lock threadId: 10 beginCounter: 2 endCounter: 3 threadId: 10 
+6
source share
1 answer

From the documentation :

The SemaphoreSlim class does not use a thread or task identifier when calling Wait, WaitAsync, and Release methods

In other words, the class does not see which thread is calling it. This is just a simple counter. The same thread can receive the semaphore several times, and it will be the same as if several threads acquired the semaphore. If the remaining number of threads remains to 0, then even if the thread was already the one that acquired the semaphore in this thread, if it calls Wait() , it will block until some other thread releases the semaphore.

So, with respect to async / await fact that a await may or may not resume in the same thread where it was started does not matter. As long as you keep the balanced calls to Wait() and Release() , they will work as you would expect and expect.

In your example, you even expect the semaphore asynchronously and thus do not block the thread. This is good, because otherwise you would slow down the UI thread the second time you clicked the button.


Related reading:
Resource locking between iterations of the main thread (Async / Await)
Why doesn't this code end in a dead end
Blocking with nested asynchronous calls

Pay attention, in particular, to reservations / recursive blocking clauses, especially with async / await . Stream synchronization is quite complicated, and the difficulty is that async / await designed to simplify. And in most cases this happens significantly. But not when you mix it with another synchronization / lock mechanism.

+11
source

Source: https://habr.com/ru/post/1012920/


All Articles