Skip to content

Instantly share code, notes, and snippets.

@asarnaout
Created March 11, 2018 15:34
Show Gist options
  • Save asarnaout/03603e8665bacac250304af3f4ded15c to your computer and use it in GitHub Desktop.
Save asarnaout/03603e8665bacac250304af3f4ded15c to your computer and use it in GitHub Desktop.
Async/Await & Concurrency Control in C#
public class Program
{
private static object Locker { get; set; }
private static async void AwaitWithLock()
{
/*
* Whenever you have code that might be contending over some resource, then you can place it in a lock statement
* to ensure thread safety. However, lock statements do not support async operations; you cannot await some task
* inside a locked code block. This was actually implemented to prevent deadlocks as the async operation might
* fail and the lock would never be released.
*/
lock (Locker)
{
//await ExpensiveOperation(); //Compilation Error "Cannot await in the body of a lock statement"
}
}
private static SemaphoreSlim MySemaphore { get; set; }
private static async void AwaitWithSemaphore(int threadId)
{
/*
* To overcome the above problem, you can use the SemaphoreSlim to control how threads can execute a block
* of code.
*
* To understand the concept of a semaphore, assume the example, where we have four rooms with identical locks and keys.
* The semaphore count - the count of keys - is set to 4 at beginning (all four rooms are free), then the count value is
* decremented as people are coming into the room. If all rooms are full, ie. there are no free keys left, the semaphore
* count is 0. Now, when one person leaves a room, the semaphore value is increased to 1 (one free key),
* and given to the next person in the queue.
*
* A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number.
* Threads can request access to the resource (decrementing the semaphore), and can signal that they have
* finished using the resource (incrementing the semaphore).
*
* One key difference between a semaphore and a mutex is that a mutex can be unlocked only by the entity
* that has locked it, a semaphore doesn't have this restriction.
*/
Console.WriteLine($"Thread {threadId} awaiting entry");
await MySemaphore.WaitAsync(); //The semaphore will allow only the specified number of threads to execute the below block of code simultaneously
Console.WriteLine($"Thread {threadId} entered successfully");
try
{
await ExpensiveOperation(threadId);
}
finally
{
MySemaphore.Release(); //The lock is released in the finally block to ensure that the lock is always released
}
}
private static Task ExpensiveOperation(int threadId)
{
return Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine($"Thread {threadId} Completed");
});
}
static Program()
{
Locker = new object();
MySemaphore = new SemaphoreSlim(1, 1);
}
public static void Main(string[] args)
{
for (var i = 0; i < 3; i ++)
{
Task.Run(() => AwaitWithSemaphore(Thread.CurrentThread.ManagedThreadId));
}
Console.ReadLine();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment