It allows you to write synchronous-looking code that uses asynchrony where appropriate. Gone is the spaghetti of callbacks, event subscriptions, and fragmented error handling. The main purpose of await is to avoid blocking while you wait for time-consuming operations to complete.
The await runs the async method on the same thread, and yet it doesn't block the thread. The trick is that the method returns as soon as it hits the await expression. When you reach the await, the code checks whether the result is already available, and if it’s not (which will almost certainly be the case), it schedules a continuation to be executed when the web operation has been completed. In fact it jumps to the end of the await expression and the continuation is executed in the UI thread and that's why the UI thread is responsive. Compiler does that by creating a state machine.
What is Continuation?
A continuation is effectively a callback to be executed when an asynchronous operation (or any Task) has been completed. It remembers the point where it reached, so it can continue from there when it’s executed. Continuations are naturally represented as delegates in .NET, and they’re typically actions that receive the results of the asynchronous operation.
Async Method Execution
The execution flow in an asynchronous method in C# follows these lines:
- Do some work.
- Start an asynchronous operation and remember the token it returns.
- Possibly do some more work. (Often, you can’t make any further progress until the asynchronous operation has been completed, in which case this step is empty.)
- Wait for the asynchronous operation to complete (via the token).
- Do some more work.
Return Task or Void
It’s useful to return a Task instead of
void because it allows the calling code to attach its own continuations to the returned task, detect when the task has failed or been completed, and so on. The ability to return
void from an async method is designed for compatibility with event handlers.
Thread pool is a capability in which there are many threads available and when we need to do something async it'll switch to one of those threads. Compared to normal threads, it is lighter and more efficient. it is always a background thread. So if we want a foreground thread or we want to perform a long task we should use the normal thread and in any other scenario, it is better to use the thread pool.
A background thread is a thread that doesn't keep the application running. In other words, if the main thread job is done and one or more background threads are running, then the application will end and destroy background threads because they don't keep up the application and they are dependent on a foreground thread.
Lock, Mutex, Semaphore
- A lock allows only one thread to enter the part that's locked and the lock is not shared with any other processes.
- A mutex is the same as a lock but it can be system-wide (shared by multiple processes).
- A semaphore does the same as a mutex but allows x number of threads to enter, this can be used for example to limit the number of CPU, IO or RAM-intensive tasks running at the same time.
Optimistic vs Pessimistic Locking
Optimistic locking is a strategy that allows multiple entities to modify the same record safely without overwriting each other's changes. You need to read the record before writing and take note of the version of that record. When you want to write back first you check to see if the current version is identical to the version you have or not. If the versions are identical, it means that no one has modified that record during the time you intended to save your change. If your version is different to the live version of the record it means that after you've received the record, someone else has modified the record so you need to get the latest record again.
You can use an integer version number and every time you modify a record you just increase the version number by one or you can use a timestamp or other unique versions. Please be careful to pick the versioning method based on the frequency of your concurrent editors. You can also use the entire state of the row as the version which eliminates having an additional version column.
Pessimistic locking is when you lock the record for your exclusive use until you have finished with it. With this approach, you need to be careful with your design to avoid deadlocks. You can use this strategy only when you have a direct connection to the database.