advertisement
Multithreading and Asynchronous Programming
C# applications often have to execute multiple tasks concurrently. For example, a C# application that simulates a car racing game must gather user inputs to navigate and move a car while concurrently moving the other cars of the game toward the finish line. Such applications, known as multi-threaded applications use multiple threads to execute multiple parts of code concurrently. In the context of programming language, a thread is a flow of control within an executing application. An application will have at least one thread known as the main thread that executes the application. A programmer can create multiple threads spawning the main thread to process tasks of the application concurrently.
A programmer can use various classes and interfaces in the System.Threading provides built-in support for multithreaded programming in the .NET Framework.
1. Thread Class
The Thread class of the System.Threading namespace allows programmers to create and control a thread in a multithreaded application. Each thread in an application passes through different states represented by the members of the ThreadState enumeration. A new thread can be instantiated by passing a ThreadStart delegate to the constructor of the Thread class. The ThreadStart delegate represents the method that the new thread will execute. Once a thread is instantiated, it can be started by calling the Start () method of the Thread class.
2. ThreadPool Class
The System.Threading namespace provides the ThreadPool class to create and share multiple threads as and when required by an application. The ThreadPool class represents a thread pool, which is a collection of threads in an application. Based on the request from the application, the thread pool assigns a thread to perform a task. When the thread completes execution, it is put back in the thread pool to be reused for another request.
The ThreadPool class contains a QueueUserWorkItem () method that a programmer can call to execute a method in a thread from the thread pool. This method accepts a WaitCallback delegate that accepts an object as its parameter. The WaitCallback delegate represents the method that must execute in a separate thread of the thread pool.
3. Synchronizing Threads
When multiple threads must share data, their activities have to be coordinated. This ensures that one thread does not change the data used by the other thread to avoid unpredictable results. For example, consider two threads in a C# program. One thread reads a customer record from a file and the other tries to update the customer record at the same time. In this scenario, the thread that is reading the customer record might not get the updated value as the other thread might be updating the record at that instance.
To avoid such situations, C# allows programmers to coordinate and manage the actions of multiple threads at a given time using the following thread synchronization mechanisms:
1. Locking using the lock Keyword:
Locking is the process that gives control to execute a block of code to one thread at one point in time. The block of code that locking protects is referred to as a critical section.
Locking can be implemented using the lock keyword. When using the lock keyword, the programmer must pass an object reference that a thread must acquire to execute the critical section. For example, to lock a section of code within an instance method, the reference to the current object can be passed to the lock.
2. Synchronization Events:
The locking mechanism used for synchronizing threads is useful for protecting critical sections of code from concurrent thread access. However, locking does not allow communication between threads. To enable communication between threads while synchronizing them, C# supports synchronization events. A synchronization event is an object that has one of two states: signaled and un-signaled. When a synchronized event is in an un-signaled state, threads can be suspended until the synchronized event comes to the signaled state.
The AutoReset Event class of the System.Threading namespace represents a synchronization event that changes automatically from signaled to un-signaled state any time a thread becomes active. The AutoRe set Event class provides the WaitOne () method that suspends the current thread from executing until the synchronized event comes to the signaled state. The Set () method of the Aut oReset Event class changes the state of the synchronized event from un-signaled to signaled.
Note: The System.Threading namespace also contains a Manual ResetEvent class that similar to the AutOResetEvent class represents a synchronization event. However, unlike the AutoResetEvent class, an object of the ManualResetEvent, class must be manually changed from signaled to un-signaled state by calling its Reset () method.
4. Task Parallel Library
Modern computers contain multiple CPUs. To take advantage of the processing power that computers with multiple CPUs deliver, a C# application must execute tasks in parallel on multiple CPUs. This is known as parallel programming. To make parallel and concurrent programming simpler, the .NET Framework introduced the Task Parallel Library (TPL). TPL is a set of public types and APIs in the System.Threading and System.Threading.Tasks namespaces.
5. Task Class
TPL provides the Task class in the System.Threading.Tasks namespace represents an asynchronous task in a program. Programmers can use this class to invoke a method asynchronously. To create a task, the programmer provides a user delegate that encapsulates the code that the task will execute.
The delegate can be a named delegate, such as the Action delegate, an anonymous method, or a lambda expression.
After creating a Task, the programmer calls the Start () method to start the task. This method passes the task to the task scheduler that assigns threads to perform the work. To ensure that a task completes before the main thread exits, a programmer can call the Wait () method of the Task class. To ensure that all the tasks of a program are completed, the programmer can call the WaitAll () method passing an array of the Tasks objects that have started.
The Task class also provides a Run() method to create and start a task in a single operation.
6. Obtaining Results from a Task
Often a C# program would require some results after a task completes its operation. To provide results of asynchronous operation, the .NET Framework provides the Task<T> class that derives from the Task class. In the Task<T> class, T is the data type of the result that will be produced. To access the result, call the Result property of the Task<T> class.
7. Task Continuation
When multiple tasks execute in parallel, it is common for one task, known as the antecedent to complete an operation and then invoke a second task, known as the continuation task. Such task continuation can be achieved by calling a Cont inueWith () overloaded methods of the antecedent task. The simplest form of the Cont inueWith () method accepts a single parameter that represents the task to be executed once the antecedent completes. The Cont inueWith () method returns the new task. A programmer can call the Wait () method on the new task to wait for it to complete.
8. Task Cancellation
TPL provides the CancellationTokenSource class in the System.Threading namespace that can be used to cancel a long-running task. The CancellationTokenSource class has a Token property that returns an object of the CancellationToken struct. This object propagates a notification that a task should be canceled. While creating a task that can be canceled, the CancellationToken object must be passed to the task
The CancellationToken struct provides an IsCancellationRequested property that returns true if a cancellation has been requested. A long-running task can query the IsCancellationrequested property to check whether a cancellation request is being made, and if so elegantly end the operation. A cancellation request can be made by calling the Cancel () method of the CancellationTokenSource class.
While canceling a task, a programmer can call the Register () method of the CancellationToken struct to register a callback method that receives a notification when the task is canceled.
9. Parallel Loops
TPL introduces a Parallel class in the system.Threading.Tasks namespace which provides methods to perform parallel computation of loops, such as for loops and each loops. The For () method is a static method in the Parallel class that enables executing a for loop with parallel iterations. As the iterations of a loop done using the For 0 method are parallel, the order of iterations might vary each time the For () method executes. The For () method has several overloaded versions. The most commonly used overloaded For 0 method accepts the following three parameters in the specified order:
- An int value represents the start index of the loop.
- An int value represents the end index of the loop.
- A System.Act ion< Int 32> delegate that is invoked once per iteration.
10. Parallel LINQ (PLINQ)
LINQ to Object refers to the use of LINQ queries with enumerable collections, such as List<T> or arrays. PLINQ is the parallel implementation of LINQ to Object. While LINQ to Object sequentially accesses an in-memory IEnumerable or IEnumerable<T> data source, PLINQ attempts parallel access to the data source based on the number of processors in the host computer. For parallel access, PLINQ partitions the data source into segments and then executes each segment through separate threads in parallel.
The Parallel Enumerable class of the System. Ling namespace provides methods that implement PLINQ functionality.
11. Concurrent Collections
The collection classes of the System. Collections. Generic namespace provides improved type safety and performance compared to the collection classes of the System. Collections namespace. However, the collection classes of the System. Collections.Generic namespace is not thread-safe. As a result, a programmer must provide thread synchronization code to ensure the integrity of the data stored in the collections. To address thread safety issues in collections, the .NET Framework provides concurrent collection classes in the system. Collections.Concurrent namespace. These classes being thread-safe relieves programmers from providing thread synchronization cod when multiple threads simultaneously access these collections. The important classes of the System. Collections. Concurrent namespace are as follows:
- ConcurrentDictionary<TKey, TValue: This is a thread-safe implementation of a dictionary of key-value pairs.
- ConcurrentQueue<T>: This is a thread-safe implementation of a queue.
- ConcurrentStack<T>: This is a thread-safe implementation of a stack.
- ConcurrentBag<T>: This is a thread-safe implementation of an unordered collection of elements.
12. Asynchronous Methods
TPL provides support for asynchronous programming through two new keywords: async and await. These keywords can be used to asynchronously invoke long-running methods in a program. A method marked with the async keyword notifies the compiler that the method will contain at least one await keyword. If the compiler finds a method marked as async but without an await keyword, it reports a compilation error.
The await keyword is applied to an operation to temporarily stop the execution of the async method until the operation completes. In the meantime, control returns to the async method's caller. Once the operation marked with await completes, execution resumes in the async method.
A method marked with the async keyword can have either one of the following return types:
- void
- Task
- Task<TResult>
Note: By convention, the method marked with the async keyword ends with an async suffix.
advertisement
Conversation
Your input fuels progress! Share your tips or experiences on prioritizing mental wellness at work. Let's inspire change together!
Join the discussion and share your insights now!
Comments 0