I have read the docs for SemaphoreSlim SemaphoreSlim MSDN which indicates that the SemaphoreSlim will limit a section of code to be run by only 1 thread at a time if you configure it as:
SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); However, it doesn't indicate if it stops the same thread from accessing that code. This comes up with async and await. If one uses await in a method, control leaves that method and returns when whatever task or thread has 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 sure seems like it stops the same thread from getting to Function2. But this is not guaranteed (as I read it) from the documentation and I wonder if that can be counted on.
I have posted 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; } } } Sample output if you click button 3 times. Notice that Function2 always finishes for a given counter before it starts again.
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 the SemaphoreSlim calls you'll 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