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