Day 1 Programming in C# 70-483
Index
- Introduction
- Multithreading and asynchronous processing
- Thread pools
- Using Tasks
- Canceling Tasks
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.
Multithreading and asynchronous processing
A process is used to isolate applications and threads run in the context of that process. This way, it makes easier for the OS to manage different apps, managing crashes and context switching (slots of time provided to each app to be executed by the CPU). The idea behind is simply run threads during a predefined slot of time, when the time permitted for an app to run is over, the CPU switches to other thread to be executed.Use the Threading class for small asynchronous tasks in your applications.
See how in the following example you can create a new thread using the System.Threading namespace. Notice how t.Join() is called to wait for the new thread to finish.
Thread.Sleep(0) is a way to force windows to switch context instead of waiting for the whole slot of time to finish.
Both elements, the process and the thread have a priority. Low priority could be useful for a screen saver. Such application shouldn't compete for CPU time. A higher priority should be used only in certain scenarios because by default it's normal priority.
Foreground and background threads are important to keep an app alive. When all foregrounds threads are finished then background threads can be terminated without waiting for them. By default a thread will be foreground type but you can change this by: myThread.IsBackground = true;
ParameterizedThreadStart is a delegate you can pass in the instantiation of your threads in order to pass parameters into your thread. In this example see how to pass an integer as a parameter in your thread:
Thread.Abort() method is used to stop a particular thread. Not a good practice because is executed by another thread and it throws a ThreadAbortException in the target, which can potentially leave a corrupt state. A better way to do this is by using a shared variable as you can see in the following example:
In the previous example we start a never ending task which will show in the screen the word "Running..." every second. The main thread of our app will stop in the ReadKey() method waiting for a key to be pressed by the user. As soon as the user press any key the "stopped" variable will turn into true, which will finish the while loop so the thread will be ended and the thread will be merged into the main app thread in th Join() call.
A thread has its own call stack that stores all the methods that are executed. Local variables are stored in the call stack and are private to the thread. But you can create global variables by decorating with the "[ThreadStatic]" attribute. These variables will act locally within the threads, it doesn't matter if you increase the value of a [ThreadStatic] integer variable in several threads because the variable is not shared between threads.
If you want to use local data and initialize it for each thread you can use the ThreadLocal<T> class. This class takes a delegate to a method that initializes the value;
See how in the previous example we use the Thread.CurrentThread class which contains the thread execution context (CultureInfo to represent dates, currency, sorting order for text, etc. for a certain culture), principal (security), priority and more. When a thread is created, the runtime ensures that the initiating thread's execution context is flowed to the new thread, so the new thread has the same privileges as the parent thread.
This copy of data has a process time cost which we can avoid if not needed by using ExecutionContext.SupressFlow method.
Thread Pools
Working directly with the Thread class, you create a new thread each time and the thread dies when you're finished with it. This creation of a new thread is an operation which has time / resources costs associated.A thread pool reuses threads. Instead of letting a thread die, you send it back to the pool where it can be used when a new request comes. The thread pool limits the available number of threads, which means, you could get lesser parallelism than using Thread class.
On the other side, imagine a web server managing too many user requests. At some point it could become unresponsive using Threads and start queuing up the work load. You can save this easily by using Thread pools because it automatically manages the amount of threads it needs and if threads are not used for a long time, it automatically removes them.
Using Tasks
Queuing a work item into a thread pool can be useful but there is no built-in way to know when the operation is finished and what the return value is. Here is when we introduce Tasks. They represent some work that has to be done. Tasks can tell you if the work is completed and what is the returning value. By default, the task scheduler uses threads from the pool.*** Use Taks when you want to keep you interface thread free (responsiveness) or you want to parallelize your work on multiple processors.
See an small example of how to use it.
Next to Task is the Task<T> class, which you can use to return a value from a task. In this example we create a Tasks<int>. Attempting to read the Result property will wait until the Task is finished before continuing.
You could save this inconvenience by adding a continuation task. This means that you want to run another operation as soon as the task finishes.
The ContinueWith method has a couple of overloads to configure when the continuation will run. Continuation methods will run when an exception happens, the Task is canceled or the Taks completes.
A Task can also have several child Tasks. The parent Task finishes when all the child tasks are ready. See an example below:
As you can see, finalTask runs after the parent Task is finished, and the parent Task finishes when all three children are finished. You had to create three Tasks all with the same options. To make the process easier you can use a TaskFactory with a certain configuration to create your tasks.
Next to Wait on a single Task, you can also use the method WaitAll to wait for multiple Tasks to finish before continuing execution. As an example: Task.WaitAll(tasks); being tasks an array of several Task objects.
Next to WaitAll, you can use WhenAll to schedule a continuation method after all Tasks have finished. This WhenAll function is used in the same way as we did before with the method ContinueWith.
Finally, instead of waiting for all of them to be finished you might want to wait until one is finished, for that reason you could use WaitAny method, see example below:
The while loop will be finished after three seconds when all the tasks are completed.
Cancelling tasks
When working with long running threaded operations, in some cases, you might want to stop the process, for this reason .Net offers you a class to signal a cancel operation within a task: CancellationToken. You just pass a CancellationToken into a class which will be periodically monitored to see if the cancellation is requested.The operation will just end when cancellation is requested. Outside users won't see anything different because the task will have a RanToCompletion state but if you want to signal those users you can trigger a OperationCanceledException by using the ThrowIfCancellationRequested() method. See here: