Created
March 11, 2018 15:34
-
-
Save asarnaout/03603e8665bacac250304af3f4ded15c to your computer and use it in GitHub Desktop.
Async/Await & Concurrency Control in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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