WHAT'S NEW?
Loading...

Day 2 Programming in C# 70-483

Index


  • Introduction
  • Using the Parallel class
  • Using async and await
  • Using the Parallel Language Integrated Query (PLINQ)
  • Using concurrent collections

Introduction

This post is part of a series of post to help you prepare the MCSD certification, particularly the certification exam 70-483, based on the book:


You will find all the code available in the following GitHub repository. Lets talk a little bit about threads and how they work using a .Net development environment.

Using the Parallel class

System.Threading.Tasks contains also another class we have not checked yet in previous posts. The Parallel class has a couple of static methods (For, ForEach and Invoke). This doesn't mean you should replace all your loops with parallel loops.

Use the parallel class only when your code doesn't have to be executed sequentially. When you have a lot of work to be done that can be executed in parallel. For smaller work sets or for work that has to synchronize access to resources, using the Parallel class can hurt performance.

In the following example you can see how to use the method For and ForEach when you want to run parallel operations without sharing resources. The third block of code is using the ParallelLoopResult to show you how to break a running operation when the loop completes the first half of iterations. At that point, the IsCompleted value equals to false and the LowestBreakIteration is 500.


Using async and await

When accessing I/O components on the primary thread, Windows typically, pauses your thread in order to release the CPU. Asynchronous solves this limitation but it has some complexity when writing code. For this reason, Microsoft introduced async and await operators when C#5 was released improving responsiveness and scalability.

A method marked with async starts on the current thread and it contains calls marked with await, which are our parallel tasks. Using the await keyword, the compiler can be aware if an operation has finished (continuing synchronously) or not (hooking up a continuation method that should run when the Task completes.

In the following example you get the taste of how this great tool works. Note: you probably need to import the System.Net.Http dll into your project if your IDE can't resolve the HttpClient class dependency



See how a separated method is decorated with the "async" keyword because the entry point for an app can't be marked as async.

Keep in mind, you are not making your app performing any better just by using tasks and awaiting them. You're improving responsiveness.

Normally, when an exception happens you expect an AggregateException to be triggered.

Another relevant point is the SynchronizationContext which connects its application model to its threading model. It abstracts how different applications work and helps you end up on the right thread working with a WPF app or ASP... This means, for example in an ASP app, tasks will find the right user related information (current user, culture,...) when get finished.

In some cases you don't need this data and could run an asynchronous task without the context. Calling the method ConfigureAwait(false) you disable the context saving process and you get better performance. See how in the following example we use a couple of awaits without saving the context. We are getting some text from internet and placing those lines in a text file.



With async/await you should keep in mind:

  1. Never return void within an async method. Return void is only valid for asynchronous events.
  2. Never marked a method as async without any await call decorated.

Using the Parallel Language Integrated Query (PLINQ)

As you are probably aware, LINQ (Language –Integrated Query) is a popular syntax used to perform queries over all kind of data. PLINQ is the parallel version and lets you run extended methods like for example: Where, Selet, SelectMany, GroupBy, Join, OrderBy, Skip and Take.

It's the runtime who decided weather to run in parallel or not. You can force to run in parallel by calling: WithExecutionMode() method. Behind the scenes the CLR will generate different Tasks objects to perform the asynchronous process.

By default, PLINQ uses all the available processor in the CPU (up to 64) but you can always limit this by calling: WithDegreeOfParallelism() method, and passing an integer which represents the number of processors to be used. Note, this parallelism does not guarantee any particular order, see following example:


There is also a way to get this results ordered by calling AsOrdered() function, which still perform a parallel action but buffering the results from the different tasks.

In some cases you might want to perform a sequential operation after a parallel one. By calling AsSequential(), PLINQ will return the answer as non parallel. In the following example we are using Take() and it might mess up the result unless we call AsSequential() first:


On the other hand, if you don't mind get not ordered results you might want to perform a ForAll() function call: parallelResult.ForAll(e => Console.WriteLine(e)); It does not need all the results before starting, but, it will remove any sort order specified. In some cases, you can end up with exceptions in your queries which will be handled using AggregateException. This will show a list of all exceptions triggered during the parallel execution.

Using concurrent collections

Asynchronous access to data always need to be concurrent safe, it means, more than one thread can modify the same data at the same time. There are some collections in .Net developers use to ensure concurrency is safe used like:

  • BlockingCollection<T>
Thread-safe entity handles access to its information. In reality it's a wrapper around a collection, by default, the ConcurrentQueue. For this example, notice how there is one task which listens for new items being added to the collection blocking if no items are available, while other task adds items to it.


You could call the CompleteAdding() method to signal that no more items will be added so other threads waiting for items won't be blocked any more. We can improve the algorithm by using GetConsumingEnumerable() method which returns an Enumerable object that blocks until it finds a new item. With this you can use a foreach with your BlockingCollection to enumerate it.

  • ConcurrentBag<T>
It is, effectively, just a bag which allow you to perform Add, TryTake and TryPeek with duplicates an no particular order. Sometimes I found TryPeek not very useful because you can end up with another tread deleting the item before you actually access it. ConcurrentBag implements IEnumerable<T> so you can iterate over an snapshot of the collection. If new items are added after you start iterate they won't be visible until you start a new iteration.
  • ConcurrentDictionary<TKey, T>
It stores key and value in a thread-safe manner, you can add, remove and update (if exist) items as atomic operations (a single operation will be performed without other threads interfering). TryUpdate() will check first if the current value is different from the new one. AddOrUpdate() will push a new item to the Dictionary if it doesn't exists. GetOrAdd() gets the current value of an item if it's available, if not, it will add the new value.
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
Stack is a LIFO collection and a Queue is a FIFO collection. ConcurrentStack implements Push() (save item) and TryPop() (get item) methods, unfortunately you can never be sure whether are items within the queue due to several threads running in parallel. You can also add or remove bulk items with: PushRange() and TryPopRange().

ConcurrentQueue provides Enqueue (push) and TryDequeue (get) to add and remove items from the queue. It also contains a TryPeek() and it implements the IEnumerable class by making a snapshot of the data.


References

https://www.microsoftpressstore.com/store/exam-ref-70-483-programming-in-c-sharp-9780735676824
https://github.com/tribet84/MCSD/tree/master/mcsd

0 comments:

Post a Comment