C# 24 Study Notes Concurrency by Asynchronous

C#

24.Concurrency by async

📌Asynchronicity and scalability

 

24.1.Implement Asynchronous method

📌What is Asynchronous method?

An asynchronous method is one that does not block the current thread on which it starts to run.

 

📌What happened after invoking an asynchronous method?

Once invokes, the method will return control to the calling environment and to perform its work on a separate thread.

 

📌What should you use?

await and async

 

24.1.1. Problem📃 & Solution🔨

The Problem

Suppose you have a method called slowMethod which is invoked by a UI event, e.g. left mouse click. Meanwhile, the methods have to do it one after another.

The preceding problem is that the UI thread(main thread) will be frozen until the 3rd method completed.

 

1️⃣Implement method with Task,

The preceding problem is that the message.Text will not wait for the task end😢. It pops up the message right after the task.Start();.

 

2️⃣Implement with Task and Wait,

The preceding problem is that the thread still waits for the task.Wait() which is meaningless. The UI thread will block the interface again.

 

3️⃣Implement with Task and define continuation

The preceding problem is "The application called an interface that was marshaled for a different thread".

What does it mean?🤔

In C#, only the main thread(UI thread) has the right to modify UI. Other threads don't have the right.

 

4️⃣Implement with Task, define continuation, and use Dispatcher. 🆗😶

The Dispatcher object is a component of the user interface infrastructure, and you can send it requests to perform work on the user interface thread by calling its RunAsync method. Although this works, but it is messy and hard to maintain the code.

 

The Solution

The keywords async and await are to tackle such problem and you don't have to concern to use Dispatcher.

 

There are few things worth discussed.

📌what should be the operand of await?

The thing right next to await is called operand, e.g. doFirstLongRunningOperation is the operand of await. The operand must be a Task. A.k.a. The return type of the method is Task.

 

📌what is the mechanism behind?

It is very similar to using Dispatcher.

async :

does - ✔️ specify that the code in the method can be divided into one or more continuations.

does not - signify that a method runs asynchronously on a separate thread

await:

does - ✔️ specifies when the C# compiler can split the code into a continuation. The right hand side of await is an awaitable object is a type that provides the GetAwaiter method.

 

🔨Solution 1

 

🔨Solution 2

The preceding solution has 1 constraint. What if I want to split one long running operation into few parallelable task?

The solution is to make the method async as well.

In the main code should look like this:

 

24.1.2. async methods return values

This refers to Task<TResult>.Result

Suppose you have a Task:

There are 2 ways you can run this method and get its value:

Solution 1🔨 Will block until the task complete

 

Solution 2🔨 ✔️

 

📌What is the difference?

Using Result would block until the task had completed.

Using await does the opposite - it unwraps a Task<T> to a T value. It won't block the thread!

 

24.1.3. async method gotchas⚠️⭐️

1️⃣ async does NOT 100% means method runs asynchronously

 

2️⃣ async does mean the method contains statements that may run asynchronously.

 

3️⃣ await indicates a method should be run by a separate task. The calling code is suspended until the method call completes.

image-20220125114741165

 

4️⃣ await is NOT Wait!⚠️ The former would not block, the latter would.

 

5️⃣ By default, the code that resumes execution after an await operator attempts to obtain the original thread.

Use ConfigureAwait(false) to specify code can be resumed on any available thread.

ConfigureAwait(true) is default.

The following is a bad example:

 

6️⃣Careless use of asynchronous methods!!⚠️

Wrong:

🆗

✔️my preference

 

24.1.4. async methods and the WinRT APIs

The designer of Windows 8 and later versions wanted to ensure the application as responsive as possible. So they decided any operations might take over 50ms have to implement they async API.

 

There are several methods can be called asynchronously.

📌Display Message

Displays the message and waits for the user to click the Close button.

📌Select File

Display the files in the user’s Documents folder and wait while the user selects a single file from this list

 

📌Open a File

Open a file in an asynchronous way:

📌Render Pixels on Screen

The pixels can be seen as stream.

 

 

24.1.5. Memory allocation with ValueTask

📌Some Resources on ValueTask

Value Task Doc

.NET Blog Understanding the Whys, Whats, and Whens of ValueTask

 

📌Case Study

Please have a look on the following method:

 

💡Pattern: Cache-Aside

The preceding method uses Cache-Aside pattern which load data on demand into a cache from a data store. This can improve performance and also helps to maintain consistency between data held in the cache and data in the underlying data store.

 

📈Analysis on this Method

In most cases, the work will be performed synchronously (it finds the data in cache). The data is an integer, but it is returned wrapped in a Task<int> object. Compared to directly return an int, the former requires much more time and memory allocation.

Return TypeOperationMemory
Task<int>1️⃣Create obj
2️⃣Populate obj
3️⃣Retrieve the data
On Steap
intreturn directlyOn Stack

 

🔨Solution

Use ValueTask which marshals the return value as a value type on stack rather than reference type on heap.

 

📌Conclusion

Return ValueTask only if the vast majority of the calls to an async method are likely to be performed synchronously. a.k.a. Most of the time, the call will return before the await operator. Otherwise, too much async operation inside a ValueTask can decrease the efficiency.

 

24.2. PLINQ to parallelize declarative data access⭐️⭐️

Use.AsParallel()! The following are examples to perform PLINQ.

24.2.1. Learn PLINQ by example

📌Example 1

The first example is to filter numbers which are over 100.

Suppose you have an array called numbers

You have a pseudo test method

In reality, the query methods always take time. Therefore, here we used Thread.SpinWait() to execute "no operation" instruction for a period of time.

Normal LINQ - old school

Normal LINQ - new school

PLINQ - old school😄

PLINQ - new school😄

 

📌Example 2

The second example is to create customer order info with 2 different sources, 1️⃣customers and 2️⃣orders.

Customers

A piece of customer info can be split by , into 6 parts which contain:

  • Customer ID
  • Customer's company
  • Address
  • City
  • Country or region
  • Postal code.

Order

A piece of order info can be split by , into 2 parts which contain:

  • Order ID
  • Customer ID
  • Date of the order

Customer Order Info

Now, we need to create a Customer-Order Info by pairing the customer info and order info with their related key.

LINQ - Old School

LINQ - New School

PLINQ - old school😄

PLINQ - new school😄

 

Some Thought🤔

[2022/01/26]I used to code in the new school style. But recently I think... the old school is quite straight forward and relevant to English...

 

24.2.2. Canceling a PLINQ query

Very easy. Just take .WithCancellation()

 

24.3. Synchronizing concurrent access to data

📌What is the risk during concurrent process?

If not doing correct, concurrent process might corrupt the data during overlapping operation.

 

📌Corrupt Data Example

The preceding method simply records the current loop index into a shared variable j, and store back the j value to current index of the array.

This is WRONG!!! Try NOT to use shared variable in concurrent process!!!⚠️

 

24.3.1. lock data

If you really need to use shared data in concurrent operation, then lockis one of the choice.

📌What is lock?

You can use lock keyword to guarantee exclusive access[^12] to resources.

 

📌lock example

 

📌How does lock work?

  • 1️⃣ the lock statement attempts to obtain a mutual-exclusion lock
  • 2️⃣ once the 1st entered item have the lock, other threads will be blocked outside of the lock and wait
  • 3️⃣ 1st entered item finished the job and left... the lock is open for another item

image-20220127093833347

 

24.3.2. Synchronization primitives

Mutual exclusion lock is one of the locking techniques. In the following, we will introduce more techniques.

Overview of synchronization primitives

Synchronizing data for multithreading

📌Different function of locking techniques

  • 1️⃣a single task has sole access to a resource, (simple exclusion lock)
  • 2️⃣multiple tasks access a resource simultaneously with controlled manner, (semaphores)
  • 3️⃣share read-only access to a resource simultaneously while guaranteeing exclusive access to modify the resource, (reader/writer locks)
Locking TechniquesSole Access ReadSole Access WriteSimultaneous Access ReadSimultaneous Access Write
simple exclusion lock✔️✔️
semaphores✔️(controlled)✔️(controlled)
reader/writer locks✔️✔️

 

📌ManualResetEventSlim Class⭐️

Fun Fact

ManualResetEventSlim is the light weight version of ManualResetEvent and that's why call it "Slim".

Function

ManualResetEventSlim provides functionality by which one or more tasks can wait for an event.

How to use it?

An object of ManualResetEventSlim can be 1 of 2 states: signaled (true) and unsignaled (false).

You can use Set() to change unsignaled to signal.

You can use Reset() to change signaled to unsignaled.

It is super similar to PLC connection!!⭐️

Example

 

 

📌SemaphoreSlim Class

Function

Represents a lightweight alternative to Semaphore that limits the number of threads that can access a resource or pool of resources concurrently.

How to use it?

Init Semaphore with the number of resources in the pool. public SemaphoreSlim(int initialCount, int maxCount)

  • when access the resource, invoke the Wait(), the gate reduce
  • when quit accessing, invoke Release(), the gate increase

Example

 

📌CountdownEvent Class

Function

Represents a synchronization primitive that is signaled when its count reaches zero. You can think of the CountdownEvent class as a cross between the inverse of SemaphoreSlim and ManualResetEventSlim.

Comparison

Why "the inverse"? Because CountdownEvent blocks Task when value >0 , while SemaphoreSlim and ManualResetEventSlim blocks Task when value 0.

Example

 

📌ReaderWriterLockSlim Class⭐️

Function

Represents a lock that is used to manage access to a resource, allowing multiple threads for reading or exclusive access for writing.

How to use it?

When a Task needs to read something, 1️⃣ EnterReadLock(), after finished, 2️⃣ExitReadLock()

When a Task needs to write something, 1️⃣ EnterWriteLock(), after finished, 2️⃣ExitWriteLock()

Example

The following example includes simple methods to add to the cache, delete from the cache, and read from the cache. To demonstrate time-outs, the example includes a method that adds to the cache only if it can do so within a specified time-out.

Now, we use the preceding class to code:

 

📌Barrier Class

//TODO

 

 

24.3.3. Cancel synchronization

The ManualResetEventSlim, SemaphoreSlim, CountdownEvent, and Barrier classes all support cancellation.

 

24.3.4. Concurrent collection classes

📌When should you use?

If you consider synchronization primitives are not scalable enough and this manual process is potentially prone, you can use System.Collections.Concurrent which is a small set of thread-safe collection classes and interfaces.

📌Notes before use

Adding thread safety to the methods in a collection class imposes additional run-time overhead, so these classes are not as fast as the regular collection classes.⚠️ So use it when you really need it.

📌What are they?

  • ConcurrentBag<T>

    • description: a general-purpose class for holding an unordered collection of items
    • function: Add, TryTake, TryPeek
  • ConcurrentDictionary<TKey, TValue>

    • description: a thread-safe version of the generic Dictionary<TKey, TValue>
    • function: TryAdd, ContainsKey, TryGetValue, TryRemove
  • ConcurrentQueue<T>

    • description: a thread-safe version of the generic Queue<T> class
    • function: Enqueue, TryDequeue, and TryPeek
  • ConcurrentStack<T>

    • description: a thread-safe implementation of the generic Stack<T>
    • function: Push, TryPop, and TryPeek

 

 

 

Previous
Previous

C# 05 Study Notes Compound and Iteration statements

Next
Next

C# in Depth 02 Generics