Pro C#10 CHAPTER 15 Multithreaded, Parallel, and Async Programming

CHAPTER 15

Multithreaded, Parallel, and Async Programming

Nobody enjoys working with an application that is sluggish during its execution. Moreover, nobody enjoys starting a task in an application (perhaps initiated by clicking a toolbar item) that prevents other parts of the program from being as responsive as possible. Before the release of .NET (and .NET Core), building applications that had the ability to perform multiple tasks typically required authoring complex C++ code that used the Windows threading APIs. Thankfully, the .NET/.NET Core platform provides several ways for you to build software that can perform complex operations on unique paths of execution, with far fewer pain points.
This chapter begins by defining the overall nature of a “multithreaded application.” Next, you will be introduced to the original threading namespace that has shipped since .NET 1.0, specifically
System.Threading. Here, you will examine numerous types (Thread, ThreadStart, etc.) that allow you to explicitly create additional threads of execution and synchronize your shared resources, which helps ensure that multiple threads can share data in a nonvolatile manner.
The remaining parts of this chapter will examine three more recent techniques .NET Core developers can use to build multithreaded software, specifically the Task Parallel Library (TPL), Parallel LINQ (PLINQ), and the relatively new (as of C# 6) intrinsic asynchronous keywords of C# (async and await). As you will see, these features can dramatically simplify how you can build responsive multithreaded software applications.

The Process/AppDomain/Context/Thread Relationship
In Chapter 14, a thread was defined as a path of execution within an executable application. While many
.NET Core applications can live happy and productive single-threaded lives, an assembly’s primary thread (spawned by the runtime when the application’s entry point executes) may create secondary threads of execution at any time to perform additional units of work. By creating additional threads, you can build more responsive (but not necessarily faster executing on single-core machines) applications.
The System.Threading namespace was released with .NET 1.0 and offers one approach to build multithreaded applications. The Thread class is perhaps the core type, as it represents a given thread. If you want to programmatically obtain a reference to the thread currently executing a given member, simply call the static Thread.CurrentThread property, like so:

static void ExtractExecutingThread()
{
// Get the thread currently
// executing this method.
Thread currThread = Thread.CurrentThread;
}

© Andrew Troelsen, Phil Japikse 2022
A. Troelsen and P. Japikse, Pro C# 10 with .NET 6, https://doi.org/10.1007/978-1-4842-7869-7_15

571

Recall that with .NET Core, there is only a single AppDomain. Even though extra AppDomain’s cannot be created, an application’s AppDomain can have numerous threads executing within it at any given time. To get a reference to the AppDomain that is hosting the application, call the static Thread.GetDomain() method, like so:

static void ExtractAppDomainHostingThread()
{
// Obtain the AppDomain hosting the current thread. AppDomain ad = Thread.GetDomain();
}

A single thread may also be moved into an execution context at any given time, and it may be relocated within a new execution context at the whim of the .NET Core Runtime. When you want to obtain the current execution context a thread happens to be executing in, use the static Thread.CurrentThread.
ExecutionContext property, like so:

static void ExtractCurrentThreadExecutionContext()
{
// Obtain the execution context under which the
// current thread is operating. ExecutionContext ctx =
Thread.CurrentThread.ExecutionContext;
}

Again, the .NET Core Runtime oversees moving threads into (and out of) execution contexts. As a .NET Core developer, you can usually remain blissfully unaware where a given thread ends up. Nevertheless, you should be aware of the various ways of obtaining the underlying primitives.

The Problem of Concurrency
One of the many “joys” (read: painful aspects) of multithreaded programming is that you have little control over how the underlying operating system or the runtime uses its threads. For example, if you craft a block of code that creates a new thread of execution, you cannot guarantee that the thread executes immediately.
Rather, such code only instructs the OS/Runtime to execute the thread as soon as possible (which is typically when the thread scheduler gets around to it).
Furthermore, given that threads can be moved between application and contextual boundaries as required by the runtime, you must be mindful of which aspects of your application are thread-volatile (e.g., subject to multithreaded access) and which operations are atomic (thread-volatile operations are the dangerous ones!).
To illustrate the problem, assume a thread is invoking a method of a specific object. Now assume that this thread is instructed by the thread scheduler to suspend its activity to allow another thread to access the same method of the same object.
If the original thread was not finished with its operation, the second incoming thread may be viewing an object in a partially modified state. At this point, the second thread is basically reading bogus data, which is sure to give way to extremely odd (and hard to find) bugs, which are even harder to replicate and debug.
Atomic operations, on the other hand, are always safe in a multithreaded environment. Sadly, there are few operations in the .NET Core base class libraries that are guaranteed to be atomic. Even the act of assigning a value to a member variable is not atomic! Unless the .NET Core documentation specifically says an operation is atomic, you must assume it is thread-volatile and take precautions.

The Role of Thread Synchronization
At this point, it should be clear that multithreaded programs are in themselves quite volatile, as numerous threads can operate on the shared resources at (more or less) the same time. To protect an application’s resources from possible corruption, .NET Core developers must use any number of threading primitives (such as locks, monitors, and the [Synchronization] attribute or language keyword support) to control access among the executing threads.
Although the .NET Core platform cannot make the difficulties of building robust multithreaded applications completely disappear, the process has been simplified considerably. Using types defined within the System.Threading namespace, the Task Parallel Library, and the C# async and await language keywords, you can work with multiple threads with minimal fuss and bother.
Before diving into the System.Threading namespace, the TPL, and the C# async and await keywords, you will begin by examining how the .NET Core delegate type can be used to invoke a method in an asynchronous manner. While it is most certainly true that since .NET 4.6 the new C# async and await keywords offer a simpler alternative to asynchronous delegates, it is still important that you know how to interact with code using this approach (trust me, there is a ton of code in production that uses asynchronous delegates).

The System.Threading Namespace
Under the .NET and .NET Core platforms, the System.Threading namespace provides types that enable the direct construction of multithreaded applications. In addition to providing types that allow you to interact with a .NET Core Runtime thread, this namespace defines types that allow access to the .NET Core Runtime– maintained thread pool, a simple (non-GUI-based) Timer class, and numerous types used to provide synchronized access to shared resources. Table 15-1 lists some of the important members of this namespace. (Be sure to consult the .NET Core SDK documentation for full details.)

Table 15-1. Core Types of the System.Threading Namespace

Type Meaning in Life
Interlocked This type provides atomic operations for variables that are shared by multiple threads.
Monitor This type provides the synchronization of threading objects using locks and wait/signals. The C# lock keyword uses a Monitor object under the hood.
Mutex This synchronization primitive can be used for synchronization between application domain boundaries.
ParameterizedThreadStart This delegate allows a thread to call methods that take any number of arguments.
Semaphore This type allows you to limit the number of threads that can access a resource concurrently.
Thread This type represents a thread that executes within the .NET Core Runtime. Using this type, you can spawn additional threads in the originating AppDomain.
ThreadPool This type allows you to interact with the .NET Core Runtime–maintained thread pool within a given process.
(continued)

Table 15-1. (continued)

Type Meaning in Life
ThreadPriority This enum represents a thread’s priority level (Highest, Normal, etc.).
ThreadStart This delegate is used to specify the method to call for a given thread. Unlike the ParameterizedThreadStart delegate, targets of ThreadStart must always have the same prototype.
ThreadState This enum specifies the valid states a thread may take (Running, Aborted, etc.).
Timer This type provides a mechanism for executing a method at specified intervals.
TimerCallback This delegate type is used in conjunction with Timer types.

The System.Threading.Thread Class
The most primitive of all types in the System.Threading namespace is Thread. This class represents an object-oriented wrapper around a given path of execution within an AppDomain. This type also defines several methods (both static and instance level) that allow you to create new threads within the current AppDomain, as well as to suspend, stop, and destroy a thread. Consider the list of key static members in Table 15-2.

Table 15-2. Key Static Members of the Thread Type

Static Member Meaning in Life
ExecutionContext This read-only property returns information relevant to the logical thread of execution, including security, call, synchronization, localization, and transaction contexts.
CurrentThread This read-only property returns a reference to the currently running thread.
Sleep() This method suspends the current thread for a specified time.

The Thread class also supports several instance-level members, some of which are shown in Table 15-3.

Table 15-3. Select Instance-Level Members of the Thread Type

Instance-Level Member Meaning in Life
IsAlive Returns a Boolean that indicates whether this thread has been started (and has not yet terminated or aborted).
IsBackground Gets or sets a value indicating whether this thread is a “background thread” (more details in just a moment).
Name Allows you to establish a friendly text name of the thread.
Priority Gets or sets the priority of a thread, which may be assigned a value from the
ThreadPriority enumeration.
(continued)

Table 15-3. (continued)

Instance-Level Member Meaning in Life
ThreadState Gets the state of this thread, which may be assigned a value from the
ThreadState enumeration.
Abort() Instructs the .NET Core Runtime to terminate the thread as soon as possible.
Interrupt() Interrupts (e.g., wakes) the current thread from a suitable wait period.
Join() Blocks the calling thread until the specified thread (the one on which Join() is called) exits.
Resume() Resumes a thread that has been previously suspended.
Start() Instructs the .NET Core Runtime to execute the thread ASAP.
Suspend() Suspends the thread. If the thread is already suspended, a call to Suspend() has no effect.

■Note aborting or suspending an active thread is generally considered a bad idea. When you do so, there is a chance (however small) that a thread could “leak” its workload when disturbed or terminated.

Obtaining Statistics About the Current Thread of Execution
Recall that the entry point of an executable assembly (i.e., the top-level statements or the Main() method) runs on the primary thread of execution. To illustrate the basic use of the Thread type, assume you have
a new Console Application project named ThreadStats. As you know, the static Thread.CurrentThread property retrieves a Thread object that represents the currently executing thread. Once you have obtained the current thread, you are able to print out various statistics, like so:

Console.WriteLine(" Primary Thread stats \n");

// Obtain and name the current thread. Thread primaryThread = Thread.CurrentThread; primaryThread.Name = "ThePrimaryThread";

// Print out some stats about this thread. Console.WriteLine("ID of current thread: {0}",
primaryThread.ManagedThreadId); Console.WriteLine("Thread Name: {0}",
primaryThread.Name);
Console.WriteLine("Has thread started?: {0}", primaryThread.IsAlive);
Console.WriteLine("Priority Level: {0}", primaryThread.Priority);
Console.WriteLine("Thread State: {0}", primaryThread.ThreadState);
Console.ReadLine();

Here is the current output:

Primary Thread stats ID of current thread: 1
Thread Name: ThePrimaryThread Has thread started?: True Priority Level: Normal
Thread State: Running

The Name Property
Notice that the Thread class supports a property called Name. If you do not set this value, Name will return an empty string. However, once you assign a friendly string moniker to a given Thread object, you can greatly simplify your debugging endeavors. If you are using Visual Studio, you may access the Threads window during a debugging session (select Debug ➤ Windows ➤ Threads when the program is running). As you can see from Figure 15-1, you can quickly identify the thread you want to diagnose.

Figure 15-1. Debugging a thread with Visual Studio

The Priority Property
Next, notice that the Thread type defines a property named Priority. By default, all threads have a priority level of Normal. However, you can change this at any point in the thread’s lifetime using the Priority property and the related System.Threading.ThreadPriority enumeration, like so:

public enum ThreadPriority
{
Lowest, BelowNormal,
Normal, // Default value. AboveNormal,
Highest
}

If you were to assign a thread’s priority level to a value other than the default (ThreadPriority.Normal), understand that you would have no direct control over when the thread scheduler switches between threads. A thread’s priority level offers a hint to the .NET Core Runtime regarding the importance of the thread’s activity. Thus, a thread with the value ThreadPriority.Highest is not necessarily guaranteed to be given the highest precedence.
Again, if the thread scheduler is preoccupied with a given task (e.g., synchronizing an object, switching threads, or moving threads), the priority level will most likely be altered accordingly. However, all things being equal, the .NET Core Runtime will read these values and instruct the thread scheduler how to best allocate time slices. Threads with an identical thread priority should each receive the same amount of time to perform their work.

In most cases, you will seldom (if ever) need to directly alter a thread’s priority level. In theory, it is possible to jack up the priority level on a set of threads, thereby preventing lower-priority threads from executing at their required levels (so use caution).

Manually Creating Secondary Threads
When you want to programmatically create additional threads to carry on some unit of work, follow this predictable process when using the types of the System.Threading namespace:
1.Create a method to be the entry point for the new thread.
2.Create a new ParameterizedThreadStart (or ThreadStart) delegate, passing the address of the method defined in step 1 to the constructor.
3.Create a Thread object, passing the ParameterizedThreadStart/ThreadStart
delegate as a constructor argument.
4.Establish any initial thread characteristics (name, priority, etc.).
5.Call the Thread.Start() method. This starts the thread at the method referenced by the delegate created in step 2 as soon as possible.
As stated in step 2, you may use two distinct delegate types to “point to” the method that the secondary thread will execute. The ThreadStart delegate can point to any method that takes no arguments and returns nothing. This delegate can be helpful when the method is designed to simply run in the background without further interaction.
The limitation of ThreadStart is that you are unable to pass in parameters for processing. However, the ParameterizedThreadStart delegate type allows a single parameter of type System.Object. Given that anything can be represented as a System.Object, you can pass in any number of parameters via a custom class or structure. Do note, however, that the ThreadStart and ParameterizedThreadStart delegates can only point to methods that return void.

Working with the ThreadStart Delegate
To illustrate the process of building a multithreaded application (as well as to demonstrate the usefulness of doing so), assume you have a Console Application project named SimpleMultiThreadApp that allows the end user to choose whether the application will perform its duties using the single primary thread or whether it will split its workload using two separate threads of execution.
Assuming you have imported the System.Threading namespace, your first step is to define a method to perform the work of the (possible) secondary thread. To keep focused on the mechanics of building multithreaded programs, this method will simply print out a sequence of numbers to the console window, pausing for approximately two seconds with each pass. Here is the full definition of the Printer class:

namespace SimpleMultiThreadApp; public class Printer
{
public void PrintNumbers()
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()", Thread.CurrentThread.Name);

// Print out numbers. Console.Write("Your numbers: "); for(int i = 0; i < 10; i++)
{
Console.Write("{0}, ", i); Thread.Sleep(2000);
}
Console.WriteLine();
}
}

Now, within Program.cs, you will add top-level statements to first prompt the user to determine whether one or two threads will be used to perform the application’s work. If the user requests a single thread, you will simply invoke the PrintNumbers() method within the primary thread. However, if the user specifies two threads, you will create a ThreadStart delegate that points to PrintNumbers(), pass this delegate object into the constructor of a new Thread object, and call Start() to inform the .NET Core Runtime this thread is ready for processing. Here is the complete implementation:

using SimpleMultiThreadApp;

Console.WriteLine(" The Amazing Thread App \n"); Console.Write("Do you want [1] or [2] threads? ");
string threadCount = Console.ReadLine();

// Name the current thread.
Thread primaryThread = Thread.CurrentThread; primaryThread.Name = "Primary";

// Display Thread info.
Console.WriteLine("-> {0} is executing Main()", Thread.CurrentThread.Name);

// Make worker class. Printer p = new Printer();

switch(threadCount)
{
case "2":
// Now make the thread. Thread backgroundThread =
new Thread(new ThreadStart(p.PrintNumbers)); backgroundThread.Name = "Secondary"; backgroundThread.Start();
break; case "1":
p.PrintNumbers(); break;
default:
Console.WriteLine("I don’t know what you want…you get 1 thread."); goto case "1";
}

// Do some additional work.
Console.WriteLine("This is on the main thread, and we are finished."); Console.ReadLine();

Now, if you run this program with a single thread, you will find that the final console output will not display the message until the entire sequence of numbers has printed to the console. As you are explicitly pausing for approximately two seconds after each number is printed, this will result in a less-than-stellar end-user experience. However, if you select two threads, the message box displays instantly, given that a unique Thread object is responsible for printing the numbers to the console.

Working with the ParameterizedThreadStart Delegate
Recall that the ThreadStart delegate can point only to methods that return void and take no arguments. While this might fit the bill in some cases, if you want to pass data to the method executing on the secondary thread, you will need to use the ParameterizedThreadStart delegate type. To illustrate, create a new Console Application project named AddWithThreads. Now, given that ParameterizedThreadStart can point to any method taking a System.Object parameter, you will create a custom type containing the numbers to be added, like so:

namespace AddWithThreads; class AddParams
{
public int a, b;

public AddParams(int numb1, int numb2)
{
a = numb1; b = numb2;
}
}

Next, create a method in the Program.cs file that will take an AddParams parameter and print the sum of the two numbers involved, as follows:

void Add(object data)
{
if (data is AddParams ap)
{
Console.WriteLine("ID of thread in Add(): {0}", Environment.CurrentManagedThreadId);

Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
}

The code within Program.cs is straightforward. Simply use ParameterizedThreadStart rather than
ThreadStart, like so:

using AddWithThreads;

Console.WriteLine(" Adding with Thread objects "); Console.WriteLine("ID of thread in Main(): {0}",
Environment.CurrentManagedThreadId);

// Make an AddParams object to pass to the secondary thread. AddParams ap = new AddParams(10, 10);
Thread t = new Thread(new ParameterizedThreadStart(Add)); t.Start(ap);

// Force a wait to let other thread finish. Thread.Sleep(5);
Console.ReadLine();

The AutoResetEvent Class
In these first few examples, there is not a clean way to know when the secondary thread has completed its work. In the last example, Sleep was called with an arbitrary time to let the other thread finish. One simple and thread-safe way to force a thread to wait until another is completed is to use the AutoResetEvent class. In the thread that needs to wait, create an instance of this class and pass in false to the constructor to signify you have not yet been notified. Then, at the point at which you are willing to wait, call the WaitOne() method. Here is the update to the Program.cs class, which will do this very thing using a static-level AutoResetEvent member variable:

AutoResetEvent _waitHandle = new AutoResetEvent(false);

Console.WriteLine(" Adding with Thread objects "); Console.WriteLine("ID of thread in Main(): {0}",
Environment.CurrentManagedThreadId); AddParams ap = new AddParams(10, 10);
Thread t = new Thread(new ParameterizedThreadStart(Add)); t.Start(ap);

// Wait here until you are notified!
_waitHandle.WaitOne(); Console.WriteLine("Other thread is done!");

Console.ReadLine();

When the other thread is completed with its workload, it will call the Set() method on the same instance of the AutoResetEvent type.

void Add(object data)
{
if (data is AddParams ap)
{

Console.WriteLine("ID of thread in Add(): {0}", Environment.CurrentManagedThreadId);

Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);

// Tell other thread we are done.
_waitHandle.Set();
}
}

Foreground Threads and Background Threads
Now that you have seen how to programmatically create new threads of execution using the System. Threading namespace, let’s formalize the distinction between foreground threads and background threads:
•Foreground threads can prevent the current application from terminating. The .NET Core Runtime will not shut down an application (which is to say, unload the hosting AppDomain) until all foreground threads have ended.
•Background threads (sometimes called daemon threads) are viewed by the .NET Core Runtime as expendable paths of execution that can be ignored at any point in time (even if they are currently laboring over some unit of work). Thus, if all
foreground threads have terminated, all background threads are automatically killed when the application domain unloads.
It is important to note that foreground and background threads are not synonymous with primary and worker threads. By default, every thread you create via the Thread.Start() method is automatically a
foreground thread. Again, this means that the AppDomain will not unload until all threads of execution have completed their units of work. In most cases, this is exactly the behavior you require.
For the sake of argument, however, assume that you want to invoke Printer.PrintNumbers() on a secondary thread that should behave as a background thread. Again, this means the method pointed to by the Thread type (via the ThreadStart or ParameterizedThreadStart delegate) should be able to halt safely as soon as all foreground threads are done with their work. Configuring such a thread is as simple as setting the IsBackground property to true.
Create a new Console Application named BackgroundThreads and copy the Printer.cs file into the new project. Update the namespace of the Printer class to BackgroundThreads, like this:

namespace BackgroundThreads; public class Printer
{

}

Update the Program.cs file to match the following:

using BackgroundThreads;

Console.WriteLine(" Background Threads \n"); Printer p = new Printer();
Thread bgroundThread =
new Thread(new ThreadStart(p.PrintNumbers));

// This is now a background thread. bgroundThread.IsBackground = true; bgroundThread.Start();

Notice that this code is not making a call to Console.ReadLine() to force the console to remain visible until you press the Enter key. Thus, when you run the application, it will shut down immediately because the Thread object has been configured as a background thread. Given that the entry point into an application (either top-level statements as shown here or a Main() method) triggers the creation of the primary foreground thread, as soon as the logic in the entry point completes, the AppDomain unloads before the secondary thread can complete its work.
However, if you comment out the line that sets the IsBackground property, you will find that each number prints to the console, as all foreground threads must finish their work before the AppDomain is unloaded from the hosting process.
For the most part, configuring a thread to run as a background type can be helpful when the worker thread in question is performing a noncritical task that is no longer needed when the main task of the program is finished. For example, you could build an application that pings an email server every few minutes for new emails, updates current weather conditions, or performs some other noncritical task.

The Issue of Concurrency
When you build multithreaded applications, your program needs to ensure that any piece of shared data is protected against the possibility of numerous threads changing its value. Given that all threads in an AppDomain have concurrent access to the shared data of the application, imagine what might happen
if multiple threads were accessing the same point of data. As the thread scheduler will force threads to suspend their work at random, what if thread A is kicked out of the way before it has fully completed its work? Thread B is now reading unstable data.
To illustrate the problem of concurrency, let’s build another Console Application project named MultiThreadedPrinting. This application will once again use the Printer class created previously, but this time the PrintNumbers() method will force the current thread to pause for a randomly generated amount of time.

namespace MultiThreadedPrinting; public class Printer
{
public void PrintNumbers()
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()", Thread.CurrentThread.Name);

// Print out numbers.
for (int i = 0; i < 10; i++)
{
// Put thread to sleep for a random amount of time. Random r = new Random();
Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i);
}
Console.WriteLine();
}
}

The calling code is responsible for creating an array of ten (uniquely named) Thread objects, each of which is making calls on the same instance of the Printer object as follows:
using MultiThreadedPrinting; Console.WriteLine("Synchronizing Threads \n"); Printer p = new Printer();
// Make 10 threads that are all pointing to the same
// method on the same object. Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++)
{
threads[i] = new Thread(new ThreadStart(p.PrintNumbers))
{
Name = $"Worker thread #{i}"
};
}
// Now start each one. foreach (Thread t in threads)
{
t.Start();
}
Console.ReadLine();
Before looking at some test runs, let’s recap the problem. The primary thread within this AppDomain begins life by spawning ten secondary worker threads. Each worker thread is told to make calls on the PrintNumbers() method on the same Printer instance. Given that you have taken no precautions to lock down this object’s shared resources (the console), there is a good chance that the current thread will be kicked out of the way before the PrintNumbers() method is able to print the complete results. Because you do not know exactly when (or if ) this might happen, you are bound to get unpredictable results. For example, you might find the output shown here:

Synchronizing Threads
-> Worker thread #3 is executing PrintNumbers()
-> Worker thread #0 is executing PrintNumbers()
-> Worker thread #1 is executing PrintNumbers()
-> Worker thread #2 is executing PrintNumbers()
-> Worker thread #4 is executing PrintNumbers()
-> Worker thread #5 is executing PrintNumbers()
-> Worker thread #6 is executing PrintNumbers()
-> Worker thread #7 is executing PrintNumbers()
-> Worker thread #8 is executing PrintNumbers()
-> Worker thread #9 is executing PrintNumbers()
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 3, 1, 2, 2, 2, 1, 2, 1, 1, 2, 2, 3, 3, 4,
3, 3, 2, 2, 3, 4, 3, 4, 5, 4, 5, 4, 4, 3, 6, 7, 2, 3, 4, 4, 4, 5, 6, 5, 3, 5, 8, 9,
6, 7, 4, 5, 6, 6, 5, 5, 5, 8, 5, 6, 7, 8, 7, 7, 6, 6, 6, 8, 9,
8, 7, 7, 7, 7, 9,
6, 8, 9,
8, 9,

9, 9,

8, 8, 7, 8, 9,
9,
9,

Now run the application a few more times and examine the output. It will most likely be different each time.

■ Note if you are unable to generate unpredictable outputs, increase the number of threads from 10 to 100 (for example) or introduce another call to Thread.Sleep() within your program. eventually, you will encounter the concurrency issue.

There are clearly some problems here. As each thread is telling the Printer to print the numerical data, the thread scheduler is happily swapping threads in the background. The result is inconsistent output. What you need is a way to programmatically enforce synchronized access to the shared resources. As you would guess, the System.Threading namespace provides several synchronization-centric types. The C# programming language also provides a keyword for the very task of synchronizing shared data in multithreaded applications.

Synchronization Using the C# lock Keyword
The first technique you can use to synchronize access to shared resources is the C# lock keyword. This keyword allows you to define a scope of statements that must be synchronized between threads. By doing so, incoming threads cannot interrupt the current thread, thus preventing it from finishing its work. The lock keyword requires you to specify a token (an object reference) that must be acquired by a thread to enter within the lock scope. When you are attempting to lock down a private instance-level method, you can simply pass in a reference to the current type, as follows:

private void SomePrivateMethod()
{
// Use the current object as the thread token. lock(this)
{
// All code within this scope is thread safe.
}
}

However, if you are locking down a region of code within a public member, it is safer (and a best practice) to declare a private object member variable to serve as the lock token, like so:

public class Printer
{
// Lock token.
private object threadLock = new object();

public void PrintNumbers()
{

// Use the lock token. lock (threadLock)
{

}
}
}

In any case, if you examine the PrintNumbers() method, you can see that the shared resource the threads are competing to gain access to is the console window. Scope all interactions with the Console type within a lock scope, as follows:

public void PrintNumbers()
{
// Use the private object lock token. lock (threadLock)
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()", Thread.CurrentThread.Name);
// Print out numbers. Console.Write("Your numbers: "); for (int i = 0; i < 10; i++)
{
Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i);
}
Console.WriteLine();
}
}

When doing this, you have effectively designed a method that will allow the current thread to complete its task. Once a thread enters into a lock scope, the lock token (in this case, a reference to the current object) is inaccessible by other threads until the lock is released after the lock scope has exited. Thus, if thread A has obtained the lock token, other threads are unable to enter any scope that uses the same lock token until thread A relinquishes the lock token.

■ Note if you are attempting to lock down code in a static method, simply declare a private static object member variable to serve as the lock token.

If you now run the application, you can see that each thread has ample opportunity to finish its business.

Synchronizing Threads
-> Worker thread #0 is executing PrintNumbers() Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #1 is executing PrintNumbers() Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

-> Worker thread #3 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #2 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #4 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #5 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #7 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #6 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #8 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-> Worker thread #9 is executing PrintNumbers()
Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

Synchronization Using the System.Threading.Monitor Type
The C# lock statement is a shorthand notation for working with the System.Threading.Monitor class. Once processed by the C# compiler, a lock scope resolves to the following (which you can verify using ildasm.exe):

public void PrintNumbers()
{
Monitor.Enter(threadLock); try
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()", Thread.CurrentThread.Name);

// Print out numbers. Console.Write("Your numbers: "); for (int i = 0; i < 10; i++)
{
Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i);
}
Console.WriteLine();
}
finally
{
Monitor.Exit(threadLock);
}
}

First, notice that the Monitor.Enter() method is the ultimate recipient of the thread token you specified as the argument to the lock keyword. Next, all code within a lock scope is wrapped within a try

block. The corresponding finally block ensures that the thread token is released (via the Monitor.Exit() method), regardless of any possible runtime exception. If you were to modify the MultiThreadPrinting program to make direct use of the Monitor type (as just shown), you would find the output is identical.
Now, given that the lock keyword seems to require less code than making explicit use of the System. Threading.Monitor type, you might wonder about the benefits of using the Monitor type directly. The short answer is control. If you use the Monitor type, you can instruct the active thread to wait for some duration of time (via the static Monitor.Wait() method), inform waiting threads when the current thread is completed (via the static Monitor.Pulse() and Monitor.PulseAll() methods), and so on.
As you would expect, in a great number of cases, the C# lock keyword will fit the bill. However, if you are interested in checking out additional members of the Monitor class, consult the .NET Core documentation.

Synchronization Using the System.Threading.Interlocked Type
Although it is always hard to believe, until you look at the underlying CIL code, assignments and simple arithmetic operations are not atomic. For this reason, the System.Threading namespace provides a type that allows you to operate on a single point of data atomically with less overhead than with the Monitor type. The Interlocked class defines the key static members shown in Table 15-4.

Table 15-4. Select Static Members of the System.Threading.Interlocked Type

Member Meaning in Life

CompareExchange() Safely tests two values for equality and, if equal, exchanges one of the values with a third
Decrement() Safely decrements a value by 1 Exchange() Safely swaps two values Increment() Safely increments a value by 1

Although it might not seem like it from the onset, the process of atomically altering a single value is quite common in a multithreaded environment. Assume you have code that increments an integer member variable named intVal. Rather than writing synchronization code such as the following:

int intVal = 5;
object myLockToken = new(); lock(myLockToken)
{
intVal++;
}

you can simplify your code via the static Interlocked.Increment() method. Simply pass in the variable to increment by reference. Do note that the Increment() method not only adjusts the value of the incoming parameter but also returns the new value.

intVal = Interlocked.Increment(ref intVal);

In addition to Increment() and Decrement(), the Interlocked type allows you to atomically assign numerical and object data. For example, if you want to assign the value of a member variable to the

value 83, you can avoid the need to use an explicit lock statement (or explicit Monitor logic) and use the
Interlocked.Exchange() method, like so:

var myInt = 27; Interlocked.Exchange(ref myInt, 83); Console.WriteLine(myInt);

Finally, if you want to test two values for equality and change the point of comparison in a thread-safe manner, you can leverage the Interlocked.CompareExchange() method as follows:

// If the value of i is currently 83, change myInt to 99. Interlocked.CompareExchange(ref myInt, 99, 83);

Programming with Timer Callbacks
Many applications have the need to call a specific method during regular intervals of time. For example, you might have an application that needs to display the current time on a status bar via a given helper function. As another example, you might want to have your application call a helper function every so often to perform noncritical background tasks such as checking for new email messages. For situations such
as these, you can use the System.Threading.Timer type in conjunction with a related delegate named
TimerCallback.
To illustrate, assume you have a Console Application project (TimerApp) that will print the current time every second until the user presses a key to terminate the application. The first obvious step is to write the method that will be called by the Timer type.

Console.WriteLine(" Working with Timer type \n"); Console.ReadLine();

static void PrintTime(object state)
{
Console.WriteLine("Time is: {0}", DateTime.Now.ToLongTimeString());
}

Notice the PrintTime() method has a single parameter of type System.Object and returns void. This is not optional, given that the TimerCallback delegate can only call methods that match this signature. The value passed into the target of your TimerCallback delegate can be any type of object (in the case of the email example, this parameter might represent the name of the Microsoft Exchange Server to interact with during the process). Also note that given that this parameter is indeed a System.Object, you can pass in multiple arguments using a System.Array or custom class/structure.
The next step is to configure an instance of the TimerCallback delegate and pass it into the Timer object. In addition to configuring a TimerCallback delegate, the Timer constructor allows you to specify the optional parameter information to pass into the delegate target (defined as a System.Object), the interval to poll the method, and the amount of time to wait (in milliseconds) before making the first call. Here is an example:

Console.WriteLine(" Working with Timer type \n");

// Create the delegate for the Timer type. TimerCallback timeCB = new TimerCallback(PrintTime);

// Establish timer settings.

Timer t = new Timer(
timeCB, // The TimerCallback delegate object.
null, // Any info to pass into the called method (null for no info). 0, // Amount of time to wait before starting (in milliseconds). 1000); // Interval of time between calls (in milliseconds).

Console.WriteLine("Hit Enter key to terminate…"); Console.ReadLine();

In this case, the PrintTime() method will be called roughly every second and will pass in no additional information to said method. Here is the output:

Working with Timer type Hit key to terminate…
Time is: 6:51:48 PM Time is: 6:51:49 PM Time is: 6:51:50 PM Time is: 6:51:51 PM Time is: 6:51:52 PM
Press any key to continue . . .

If you did want to send in some information for use by the delegate target, simply substitute the null
value of the second constructor parameter with the appropriate information, like so:

// Establish timer settings.
Timer t = new Timer(timeCB, "Hello From C# 10.0", 0, 1000);

You can then obtain the incoming data as follows:

static void PrintTime(object state)
{
Console.WriteLine("Time is: {0}, Param is: {1}", DateTime.Now.ToLongTimeString(), state.ToString());
}

Using a Stand-Alone Discard (New 7.0)
In the previous example, the Timer variable is not used in any execution path, so it can be replaced with a discard, as follows:

_ = new Timer(
timeCB, // The TimerCallback delegate object.
"Hello from C# 10.0", // Any info to pass into the called method
// (null for no info).
0, // Amount of time to wait before starting
//(in milliseconds).
1000); // Interval of time between calls
//(in milliseconds).

Understanding the ThreadPool
The next thread-centric topic you will examine in this chapter is the role of the runtime thread pool. There is a cost with starting a new thread, so for purposes of efficiency, the thread pool holds onto created (but inactive) threads until needed. To allow you to interact with this pool of waiting threads, the System.
Threading namespace provides the ThreadPool class type.
If you want to queue a method call for processing by a worker thread in the pool, you can use the ThreadPool.QueueUserWorkItem() method. This method has been overloaded to allow you to specify an optional System.Object for custom state data in addition to an instance of the WaitCallback delegate.

public static class ThreadPool
{

public static bool QueueUserWorkItem(WaitCallback callBack); public static bool QueueUserWorkItem(WaitCallback callBack,
object state);
}

The WaitCallback delegate can point to any method that takes a System.Object as its sole parameter (which represents the optional state data) and returns nothing. Note that if you do not provide a System. Object when calling QueueUserWorkItem(), the .NET Core Runtime automatically passes a null value. To illustrate queuing methods for use by the .NET Core Runtime thread pool, ponder the following program (in a console application called ThreadPoolApp), which uses the Printer type once again (make sure to update the namespace to ThreadPoolApp). In this case, however, you are not manually creating an array of Thread objects; rather, you are assigning members of the pool to the PrintNumbers() method.

using ThreadPoolApp;
Console.WriteLine(" Fun with the .NET Core Runtime Thread Pool \n"); Console.WriteLine("Main thread started. ThreadID = {0}",
Environment.CurrentManagedThreadId); Printer p = new Printer();
WaitCallback workItem = new WaitCallback(PrintTheNumbers);

// Queue the method ten times. for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(workItem, p);
}
Console.WriteLine("All tasks queued"); Console.ReadLine();

static void PrintTheNumbers(object state)
{
Printer task = (Printer)state; task.PrintNumbers();
}

At this point, you might be wondering if it would be advantageous to use the .NET Core Runtime– maintained thread pool rather than explicitly creating Thread objects. Consider these benefits of leveraging the thread pool:
• The thread pool manages threads efficiently by minimizing the number of threads that must be created, started, and stopped.
• By using the thread pool, you can focus on your business problem rather than the application’s threading infrastructure.
However, using manual thread management is preferred in some cases. Here is an example:
• If you require foreground threads or must set the thread priority. Pooled threads are
always background threads with default priority (ThreadPriority.Normal).
• If you require a thread with a fixed identity to abort it, suspend it, or discover it by name.
That wraps up your investigation of the System.Threading namespace. To be sure, understanding the topics presented thus far in the chapter (especially during your examination of concurrency issues) will be extremely valuable when creating a multithreaded application. Given this foundation, you will now turn your attention to several new thread-centric topics that were introduced in .NET 4.0 and carried forward to
.NET Core. To begin, you will examine the role of an alternative threading model, the Task Parallel Library.

Parallel Programming Using the Task Parallel Library
At this point in the chapter, you have examined the System.Threading namespace objects that allow you to build multithreaded software. Beginning with the release of .NET 4.0, Microsoft introduced a new approach to multithreaded application development using a parallel programming library termed the Task Parallel Library (TPL). Using the types of System.Threading.Tasks, you can build fine-grained, scalable parallel code without having to work directly with threads or the thread pool.
This is not to say, however, that you will not use the types of System.Threading when you use the TPL. Both threading toolkits can work together quite naturally. This is especially true in that the System. Threading namespace still provides most of the synchronization primitives you examined previously (Monitor, Interlocked, etc.). However, you will quite likely find that you will favor working with the TPL
rather than the original System.Threading namespace, given that the same set of tasks can be performed in a more straightforward manner.

The System.Threading.Tasks Namespace
Collectively speaking, the types of System.Threading.Tasks are referred to as the Task Parallel Library. The TPL will automatically distribute your application’s workload across available CPUs dynamically, using the runtime thread pool. The TPL handles the partitioning of the work, thread scheduling, state management, and other low-level details. The result is that you can maximize the performance of your .NET Core applications while being shielded from many of the complexities of directly working with threads.

The Role of the Parallel Class
A key class of the TPL is System.Threading.Tasks.Parallel. This class contains methods that allow you to iterate over a collection of data (specifically, an object implementing IEnumerable) in a parallel fashion, mainly through two primary static methods, Parallel.For() and Parallel.ForEach(), each of which defines numerous overloaded versions.

These methods allow you to author a body of code statements that will be processed in a parallel manner. In concept, these statements are the same sort of logic you would write in a normal looping construct (via the for or foreach C# keyword). The benefit is that the Parallel class will pluck threads from the thread pool (and manage concurrency) on your behalf.
Both methods require you to specify an IEnumerable- or IEnumerable-compatible container that holds the data you need to process in a parallel manner. The container could be a simple array, a nongeneric collection (such as ArrayList), a generic collection (such as List), or the results of a LINQ query.
In addition, you will need to use the System.Func and System.Action delegates to specify the target method that will be called to process the data. You have already encountered the Func delegate in Chapter 13, during your investigation of LINQ to Objects. Recall that Func represents a method that
can have a given return value and a varied number of arguments. The Action delegate is like Func, in that it allows you to point to a method taking some number of parameters. However, Action specifies a method that can only return void.
While you could call the Parallel.For() and Parallel.ForEach() methods and pass a strongly typed Func or Action delegate object, you can simplify your programming by using a fitting C# anonymous method or lambda expression.

Data Parallelism with the Parallel Class
The first way to use the TPL is to perform data parallelism. Simply put, this term refers to the task of iterating over an array or collection in a parallel manner using the Parallel.For() or Parallel.ForEach() method. Assume you need to perform some labor-intensive file I/O operations. Specifically, you need to load a large number of *.jpg files into memory, flip them upside down, and save the modified image data to a new location.
In this example, you will see how to perform the same overall task using a graphical user interface, so you can examine the use of “anonymous delegates” to allow secondary threads to update the primary user interface thread (aka the UI thread).

■ Note When you are building a multithreaded graphical user interface (gui) application, secondary threads can never directly access user interface controls. the reason is that controls (buttons, text boxes, labels, progress bars, etc.) have thread affinity with the thread that created them. in the following example, i will illustrate one way to allow secondary threads to access ui items in a thread-safe manner. you will see a more simplified approach when you examine the C# async and await keywords.

To illustrate, create a new Windows Presentation Foundation application (the template is abbreviated to WPF App (.NET Core)) named DataParallelismWithForEach. To create the project using the CLI and add it to the chapter’s solution, enter the following command:

dotnet new wpf -lang c# -n DataParallelismWithForEach -o .\DataParallelismWithForEach -f net6.0
dotnet sln .\Chapter15_AllProjects.sln add .\DataParallelismWithForEach

■ Note Windows presentation Foundation (WpF) is Windows only in this version of .net Core and will be covered in detail in Chapters 24–28. if you have not worked with WpF or you don’t have access to a Windows machine, everything you need for this example is listed here. if you would rather just follow along with a completed solution, you can find dataparallelismWithForeach in the Chapter 15 folder of the github repo. WpF development works with Visual studio Code, although there is no designer support. For a richer development experience, i suggest you use Visual studio 2022 for the WpF examples in this chapter.

At the time of this writing, the WPF project templates do not add support for global implicit using statements. Update the main PropertyGroup in the DataParallelismWithForEach.csproj file to the following, which also disables nullable reference types (updates in bold):


WinExe
net6.0-windows
enable
disable
true

Double-click the MainWindow.xaml file in Solution Explorer, and replace the XAML with the following:

<Window x:Class="DataParallelismWithForEach.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataParallelismWithForEach" mc:Ignorable="d"
Title="Fun with TPL" Height="400" Width="800">















Again, do not worry about what the markup means or how it works; you will spend plenty of time with WPF later in this book. The GUI of the application consists of a multiline TextBox and a single Button (named cmdProcess). The purpose of the text area is to allow you to enter data while the work is being performed in the background, thus illustrating the nonblocking nature of the parallel task.
For this example, an additional NuGet package (System.Drawing.Common) is required. To add it to your project, enter the following line (all on one line) in the command line (in the same directory as your solution file) or Package Manager Console in Visual Studio:

dotnet add DataParallelismWithForEach package System.Drawing.Common

Open the MainWindow.xaml.cs file (double-click it in Visual Studio—you might have to expand the arrow by MainWindow.xaml), and add the following using statements to the top of the file:

using System.Drawing; using System.Windows; using System.IO;

■ Note you should update the string passed into the following Directory.GetFiles() method call to point to a path on your computer that has some image files (such as a personal folder of family pictures). i have included some sample images (that ship with the Windows operating system) in the chapter’s sample code for your convenience.

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void cmdCancel_Click(object sender, EventArgs e)
{
// This will be updated shortly
}

private void cmdProcess_Click(object sender, EventArgs e)
{
this.Title = $"Starting…"; ProcessFiles();
this.Title = "Processing Complete";
}

private void ProcessFiles()
{
// Load up all .jpg files, and make a new folder for the
// modified data.
//Get the directory path where the file is executing
//For VS 2022 debugging, the current directory will be \bin\debug\ net6.0-windows
//For VS Code or “dotnet run”, the current directory will be var basePath = Directory.GetCurrentDirectory();
var pictureDirectory = Path.Combine(basePath, "TestPictures");
var outputDirectory =
Path.Combine(basePath, "ModifiedPictures");
//Clear out any existing files
if (Directory.Exists(outputDirectory))
{
Directory.Delete(outputDirectory, true);
}
Directory.CreateDirectory(outputDirectory);
string[] files = Directory.GetFiles(pictureDirectory, "
.jpg", SearchOption.AllDirectories);

// Process the image data in a blocking manner. foreach (string currentFile in files)
{
string filename = Path.GetFileName(currentFile);
// Print out the ID of the thread processing the current image.
this.Title = $"Processing {filename} on thread {Environment.CurrentManagedThreadId}"; using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); bitmap.Save(Path.Combine(outputDirectory, filename));
}
}
}
}

■ Note if you receive an error that Path is an ambiguous reference between System.IO.Path and
System.Windows.Shapes.Path, either remove the using for System.Windows.Shapes or add System.IO to Path: System.IO.Path.Combine(…).

Notice that the ProcessFiles() method will rotate each *.jpg file under the specified directory.
Currently, all the work is happening on the primary thread of the executable. Therefore, when the button to process the files is clicked, the program will appear to hang. Furthermore, the caption of the window will also report that the same primary thread is processing the file, as we have only a single thread of execution.
To process the files on as many CPUs as possible, you can rewrite the current foreach loop to use Parallel.ForEach(). Recall that this method has been overloaded numerous times; however, in the simplest form, you must specify the IEnumerable-compatible object that contains the items to process (that would be the files string array) and an Action delegate that points to the method that will perform the work.

Here is the relevant update, using the C# lambda operator in place of a literal Action delegate object. Notice that you are currently commenting out the line of code that displayed the ID of the thread executing the current image file. See the next section to find out the reason why.

// Process the image data in a parallel manner!
Parallel.ForEach(files, currentFile =>
{
string filename = Path.GetFileName(currentFile);

// This code statement is now a problem! See next section.
// this.Title = $"Processing {filename} on thread
// {Environment.CurrentManagedThreadId}";

using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); bitmap.Save(Path.Combine(outputDirectory, filename));
}
}
);

Accessing UI Elements on Secondary Threads
You will notice that I have commented out the previous line of code that updated the caption of the main window with the ID of the currently executing thread. As noted previously, GUI controls have “thread affinity” with the thread that created them. If secondary threads attempt to access a control they did not directly create, it will cause a runtime exception. However, this isn’t guaranteed to happen, and exceptions might not be raised. Threading issues depend on a lot of different variables and are typically intermittent and very hard to reproduce.

■ Note let me reiterate the previous point: when you debug a multithreaded application, you can sometimes catch errors that arise when a secondary thread is “touching” a control created on the primary thread. however, oftentimes when you run the application, the application could appear to run correctly (or it might error straightaway). until you take precautions (examined next), your application has the potential of raising a runtime error under such circumstances.

One approach that you can use to allow these secondary threads to access the controls in a thread- safe manner is yet another delegate-centric technique, specifically an anonymous delegate. The Control parent class in WPF defines a Dispatcher object, which manages the work items for a thread. This object has a method named Invoke(), which takes a System.Delegate as input. You can call this method when you are in a coding context involving secondary threads to provide a thread-safe manner to update the UI of the given control. Now, while you could write all the required delegate code directly, most developers use expression syntax as a simple alternative. Here is the relevant update to the content with the previously commented-out code statement:

// Eek! This will not work anymore!
//this.Title = $"Processing {filename} on thread {Environment.CurrentManagedThreadId}";

// Invoke on the Form object, to allow secondary threads to access controls
// in a thread-safe manner.
Dispatcher?.Invoke(() =>
{
this.Title = $"Processing {filename}";
});
using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); bitmap.Save(Path.Combine(outputDirectory, filename));
}

Now, if you run the program, the TPL will indeed distribute the workload to multiple threads from the thread pool, using as many CPUs as possible. However, since the Title is always updated from the main thread, the Title update code no longer displays the current thread, and you will not see anything if you type in the text box until all the images have been processed! The reason is that the primary UI thread is still blocked, waiting for all the other threads to finish up their business.

The Task Class
The Task class allows you to easily invoke a method on a secondary thread and can be used as a simple alternative to using asynchronous delegates. Update the Click handler of your Button control as so:

private void cmdProcess_Click(object sender, EventArgs e)
{
this.Title = $"Starting…";
// Start a new "task" to process the files. Task.Factory.StartNew(() => ProcessFiles());
//Can also be written this way
//Task.Factory.StartNew(ProcessFiles);

}

The Factory property of Task returns a TaskFactory object. When you call its StartNew() method, you pass in an Action delegate (here, hidden away with a fitting lambda expression) that points to the method to invoke in an asynchronous manner. With this small update, you will now find that the window’s
title will show which thread from the thread pool is processing a given file, and better yet, the text area is able to receive input, as the UI thread is no longer blocked.

Handling Cancellation Request
One improvement you can make to the current example is to provide a way for the user to stop the processing of the image data, via a second (aptly named) Cancel button. Thankfully, the Parallel. For() and Parallel.ForEach() methods both support cancellation with cancellation tokens. When you invoke methods on Parallel, you can pass in a ParallelOptions object, which in turn contains a CancellationTokenSource object.

First, define the following new private member variable in your Form-derived class of type
CancellationTokenSource named _cancelToken:

public partial class MainWindow :Window
{
// New Window-level variable.
private CancellationTokenSource _cancelToken = new CancellationTokenSource();

}

Update the Cancel button Click event to the following code:

private void cmdCancel_Click(object sender, EventArgs e)
{
// This will be used to tell all the worker threads to stop!
_cancelToken.Cancel();
}

Now, the real modifications need to occur within the ProcessFiles() method. Consider the final implementation:

private void ProcessFiles()
{
// Use ParallelOptions instance to store the CancellationToken. ParallelOptions parOpts = new ParallelOptions(); parOpts.CancellationToken = _cancelToken.Token; parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

// Load up all .jpg files, and make a new folder for the modified data. string[] files = Directory.GetFiles(@".\TestPictures", ".jpg", SearchOption. AllDirectories);
string outputDirectory = @".\ModifiedPictures"; Directory.CreateDirectory(outputDirectory);

try
{
// Process the image data in a parallel manner! Parallel.ForEach(files, parOpts, currentFile =>
{
parOpts
.CancellationToken.ThrowIfCancellationRequested();

string filename = Path.GetFileName(currentFile); Dispatcher?.Invoke(() =>
{
this.Title =
$"Processing {filename} on thread {Environment.CurrentManagedThreadId}";
});
using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);

bitmap.Save(Path.Combine(outputDirectory, filename));
}
});
Dispatcher?.Invoke(()=>this.Title = "Done!");
}
catch (OperationCanceledException ex)
{
Dispatcher?.Invoke(()=>this.Title = ex.Message);
}
}

Notice that you begin the method by configuring a ParallelOptions object, setting the CancellationToken property to use the CancellationTokenSource token. Also note that when you call the Parallel.ForEach() method, you pass in the ParallelOptions object as the second parameter.
Within the scope of the looping logic, you make a call to ThrowIfCancellationRequested() on the token, which will ensure if the user clicks the Cancel button, all threads will stop, and you will be notified via a runtime exception. When you catch the OperationCanceledException error, you will set the text of the main window to the error message.

■ Note new in C# 10, there is a new async method on the Parallel class named ForEachAsync(). this is covered later in this chapter.

Task Parallelism Using the Parallel Class
In addition to data parallelism, the TPL can also be used to easily fire off any number of asynchronous tasks using the Parallel.Invoke() method. This approach is a bit more straightforward than using members from System.Threading; however, if you require more control over the way tasks are executed, you could forgo use of Parallel.Invoke() and use the Task class directly, as you did in the previous example.
To illustrate task parallelism, create a new console application called MyEBookReader and be sure the System.Text and System.Net namespaces are imported at the top of Program.cs (this example is a
modification of a useful example in the .NET Core documentation). Here, you will fetch a publicly available ebook from Project Gutenberg (www.gutenberg.org) and then perform a set of lengthy tasks in parallel.
The book is downloaded in the GetBook() method, shown here:

using System.Net; using System.Text;

string _theEBook = ""; GetBook();
Console.WriteLine("Downloading book…"); Console.ReadLine();

void GetBook()
{
//NOTE: The WebClient is obsolete.
//We will revisit this example using HttpClient when we discuss async/await using WebClient wc = new WebClient();
wc.DownloadStringCompleted += (s, eArgs) =>

{
_theEBook = eArgs.Result; Console.WriteLine("Download complete."); GetStats();
};

// The Project Gutenberg EBook of A Tale of Two Cities, by Charles Dickens
// You might have to run it twice if you’ve never visited the site before, since the first
// time you visit there is a message box that pops up, and breaks this code.
wc.DownloadStringAsync(new Uri("http://www.gutenberg.org/files/98/98-0.txt"));
}

The WebClient class is a member of System.Net. This class provides methods for sending data to and receiving data from a resource identified by a URI. As it turns out, many of these methods have an asynchronous version, such as DownloadStringAsync(). This method will spin up a new thread from the
.NET Core Runtime thread pool automatically. When the WebClient is done obtaining the data, it will fire the
DownloadStringCompleted event, which you are handling here using a C# lambda expression. If you were to call the synchronous version of this method (DownloadString()), the “Downloading” message would not show until the download was complete.

■ Note the WebClient is obsolete and has been replaced by the HttpClient. We will revisit this example using the HttpClient class in the “async Calls with async/await” section of this chapter.

Next, the GetStats() method is implemented to extract the individual words contained in the theEBook
variable and then pass the string array to a few helper functions for processing as follows:

void GetStats()
{
// Get the words from the ebook.
string[] words = _theEBook.Split(new char[]
{ ‘ ‘, ‘\u000A’, ‘,’, ‘.’, ‘;’, ‘:’, ‘-‘, ‘?’, ‘/’ },
StringSplitOptions.RemoveEmptyEntries);

// Now, find the ten most common words.
string[] tenMostCommon = FindTenMostCommon(words);

// Get the longest word.
string longestWord = FindLongestWord(words);

// Now that all tasks are complete, build a string to show all stats. StringBuilder bookStats = new StringBuilder("Ten Most Common Words are:\n"); foreach (string s in tenMostCommon)
{
bookStats.AppendLine(s);
}

bookStats.AppendFormat("Longest word is: {0}", longestWord); bookStats.AppendLine(); Console.WriteLine(bookStats.ToString(), "Book info");
}

The FindTenMostCommon() method uses a LINQ query to obtain a list of string objects that occur most often in the string array, while FindLongestWord() locates, well, the longest word.

string[] FindTenMostCommon(string[] words)
{
var frequencyOrder = from word in words
where word.Length > 6 group word by word into g
orderby g.Count() descending select g.Key;
string[] commonWords = (frequencyOrder.Take(10)).ToArray(); return commonWords;
}
string FindLongestWord(string[] words)
{
return (from w in words orderby w.Length descending select w).FirstOrDefault();
}

If you were to run this project, performing all the tasks could take a significant amount of time, based on the CPU count of your machine and overall processor speed. Eventually, you should see the output shown here:

Downloading book…
Download complete.
Ten Most Common Words are:
Defarge himself Manette through nothing business another looking prisoner Cruncher
Longest word is: undistinguishable

You can help ensure that your application uses all available CPUs on the host machine by invoking the FindTenMostCommon() and FindLongestWord() methods in parallel. To do so, modify your GetStats() method as so:

void GetStats()
{
// Get the words from the ebook. string[] words = _theEBook.Split(
new char[] { ‘ ‘, ‘\u000A’, ‘,’, ‘.’, ‘;’, ‘:’, ‘-‘, ‘?’, ‘/’ },
StringSplitOptions.RemoveEmptyEntries); string[] tenMostCommon = null;
string longestWord = string.Empty;

Parallel.Invoke( () =>
{
// Now, find the ten most common words. tenMostCommon = FindTenMostCommon(words);
},
() =>
{
// Get the longest word.
longestWord = FindLongestWord(words);
});

// Now that all tasks are complete, build a string to show all stats.

}

The Parallel.Invoke() method expects a parameter array of Action<> delegates, which you have supplied indirectly using lambda expressions. Again, while the output is identical, the benefit is that the TPL will now use all possible processors on the machine to invoke each method in parallel if possible.

Parallel LINQ Queries (PLINQ)
To wrap up your look at the TPL, be aware that there is another way you can incorporate parallel tasks into your .NET Core applications. If you choose, you can use a set of extension methods that allow you to construct a LINQ query that will perform its workload in parallel (if possible). Fittingly, LINQ queries that are designed to run in parallel are termed PLINQ queries.
Like parallel code authored using the Parallel class, PLINQ has the option of ignoring your request to process the collection in parallel if need be. The PLINQ framework has been optimized in numerous ways, which includes determining whether a query would, in fact, perform faster in a synchronous manner.
At runtime, PLINQ analyzes the overall structure of the query, and if the query is likely to benefit from parallelization, it will run concurrently. However, if parallelizing a query would hurt performance, PLINQ just runs the query sequentially. If PLINQ has a choice between a potentially expensive parallel algorithm or an inexpensive sequential algorithm, it chooses the sequential algorithm by default.
The necessary extension methods are found within the ParallelEnumerable class of the System.Linq
namespace. Table 15-5 documents some useful PLINQ extensions.

Table 15-5. Select Members of the ParallelEnumerable Class

Member Meaning in Life
AsParallel() Specifies that the rest of the query should be parallelized, if possible
WithCancellation() Specifies that PLINQ should periodically monitor the state of the provided cancellation token and cancel execution if it is requested
WithDegreeOfParallelism() Specifies the maximum number of processors that PLINQ should use to parallelize the query
ForAll() Enables results to be processed in parallel without first merging back to the consumer thread, as would be the case when enumerating a LINQ result using the foreach keyword

To see PLINQ in action, create a console application named PLINQDataProcessingWithCancellation. When processing starts, the program will fire off a new Task, which executes a LINQ query that investigates a large array of integers, looking for only the items where x % 3 == 0 is true. Here is a nonparallel version of the query:

Console.WriteLine("Start any key to start processing"); Console.ReadKey();

Console.WriteLine("Processing"); Task.Factory.StartNew(ProcessIntData); Console.ReadLine();

void ProcessIntData()
{
// Get a very large array of integers.
int[] source = Enumerable.Range(1, 10_000_000).ToArray();
// Find the numbers where num % 3 == 0 is true, returned
// in descending order. int[] modThreeIsZero = (
from num in source where num % 3 == 0 orderby num descending select num).ToArray();
Console.WriteLine($"Found { modThreeIsZero.Count()} numbers that match query!");
}

Opting in to a PLINQ Query
If you want to inform the TPL to execute this query in parallel (if possible), you will want to use the
AsParallel() extension method as so:

int[] modThreeIsZero = (
from num in source.AsParallel() where num % 3 == 0
orderby num descending select num).ToArray();

Notice how the overall format of the LINQ query is identical to what you saw in previous chapters.
However, by including a call to AsParallel(), the TPL will attempt to pass the workload off to any available CPU.

Cancelling a PLINQ Query
It is also possible to use a CancellationTokenSource object to inform a PLINQ query to stop processing under the correct conditions (typically because of user intervention). Declare a class-level
CancellationTokenSource object named _cancelToken and update the top-level statements method to take input from the user. Here is the relevant code update:

CancellationTokenSource _cancelToken = new CancellationTokenSource();

do
{
Console.WriteLine("Start any key to start processing"); Console.ReadKey();
Console.WriteLine("Processing"); Task.Factory.StartNew(ProcessIntData); Console.Write("Enter Q to quit: "); string answer = Console.ReadLine();
// Does user want to quit? if (answer.Equals("Q",
StringComparison.OrdinalIgnoreCase))
{
_cancelToken.Cancel(); break;
}
}
while (true); Console.ReadLine();
Now, inform the PLINQ query that it should be on the lookout for an incoming cancellation request by chaining on the WithCancellation() extension method and passing in the token. In addition, you will want to wrap this PLINQ query in a proper try/catch scope and deal with the possible exception. Here is the final version of the ProcessIntData() method:

void ProcessIntData()
{
// Get a very large array of integers.
int[] source = Enumerable.Range(1, 10_000_000).ToArray();
// Find the numbers where num % 3 == 0 is true, returned
// in descending order. int[] modThreeIsZero = null; try
{
modThreeIsZero = (from num in source.AsParallel().WithCancellation(_cancelToken.Token) where num % 3 == 0
orderby num descending select num).ToArray();
Console.WriteLine();
Console.WriteLine($"Found {modThreeIsZero.Count()} numbers that match query!");
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
}

When running this, you will want to hit Q and enter quickly to see the message from the cancellation token. On my development machine, I had less than one second to quit before it finished on its own. You can add a call to Thread.Sleep() to make it easier to cancel the process:

try
{
Thread.Sleep(3000);
modThreeIsZero = (from num in source.AsParallel().WithCancellation(_cancelToken.Token) where num % 3 == 0
orderby num descending select num).ToArray();
Console.WriteLine();
Console.WriteLine($"Found {modThreeIsZero.Count()} numbers that match query!");
}

Async Calls Using the async/await Pattern
I have covered a lot of material in this (rather lengthy) chapter. To be sure, building, debugging, and understanding complex multithreaded applications are challenging in any framework. While the TPL, PLINQ, and the delegate type can simplify matters to some extent (especially when compared to other platforms and languages), developers are still required to know the ins and outs of various advanced techniques.
Since the release of .NET 4.5, the C# programming language has been updated with two new keywords that further simplify the process of authoring asynchronous code. In contrast to all the examples in this chapter, when you use the new async and await keywords, the compiler will generate a good deal of threading code on your behalf, using numerous members of the System.Threading and System.Threading. Tasks namespaces.

A First Look at the C# async and await Keywords (Updated 7.1, 9.0)
The async keyword of C# is used to qualify that a method, lambda expression, or anonymous method should be called in an asynchronous manner automatically. Yes, it is true. Simply by marking a method with the async modifier, the .NET Core Runtime will create a new thread of execution to handle the task at hand. Furthermore, when you are calling an async method, the await keyword will automatically pause the
current thread from any further activity until the task is complete, leaving the calling thread free to continue.
To illustrate, create a console application named FunWithCSharpAsync and add a method named
DoWork(), which forces the calling thread to wait for five seconds. Here is the story thus far:

Console.WriteLine(" Fun With Async ===>"); Console.WriteLine(DoWork()); Console.WriteLine("Completed"); Console.ReadLine();

static string DoWork()
{
Thread.Sleep(5_000); return "Done with work!";
}

Now, given your work in this chapter, you know that if you were to run the program, you would need to wait five seconds before anything else can happen. If this were a graphical application, the entire screen would be locked until the work was completed.
If you were to use any of the previous techniques shown in this chapter to make your program more responsive, you would have a good deal of work ahead of you. However, since .NET 4.5, you can author the following C# code base:


string message = await DoWorkAsync(); Console.WriteLine(message);

static async Task DoWorkAsync()
{
return await Task.Run(() =>
{
Thread.Sleep(5_000); return "Done with work!";
});
}

If you are using a Main() method as your entry point (instead of top-level statements), you need to mark the method as async, introduced in C# 7.1.

static async Task Main(string[] args)
{

string message = await DoWorkAsync(); Console.WriteLine(message);

}

■ Note the ability to decorate the Main() method with async is new as of C# 7.1. top-level statements, added in C# 9.0, are implicitly async.

Notice the await keyword before naming the method that will be called in an asynchronous manner. This is important: if you decorate a method with the async keyword but do not have at least one internal await-centric method call, you have essentially built a synchronous method call (in fact, you will be given a compiler warning to this effect).
Now, notice that you are required to use the Task class from the System.Threading.Tasks
namespace to refactor your Main() (if you are using Main()) and DoWork() methods (the latter is added as DoWorkAsync()). Basically, rather than returning a specific return value straightaway (a string object in the current example), you return a Task object, where the generic type parameter T is the underlying, actual return value. If the method does not have a return value (as in the Main() method), then instead of Task you just use Task.
The implementation of DoWorkAsync() now directly returns a Task object, which is the return value of Task.Run(). The Run() method takes a Func<> or Action<> delegate, and as you know by this point in the text, you can simplify your life by using a lambda expression. Basically, your new version of DoWorkAsync() is essentially saying the following:

When you call me, I will run a new task. This task will cause the calling thread to sleep for five seconds, and when it is done, it gives me a string return value. I will put this string in a new Task object and return it to the caller.
Having translated this new implementation of DoWorkAsync() into more natural (poetic) language, you gain some insight into the real role of the await token. This keyword will always modify a method that
returns a Task object. When the flow of logic reaches the await token, the calling thread is suspended in this method until the call completes. If this were a graphical application, the user could continue to use the UI while the DoWorkAsync() method executes.

SynchronizationContext and async/await
The official definition of the SynchronizationContext is a base class that provides free-threaded context with no synchronization. While that initial definition is not very descriptive, the official documentation goes on to say:

The purpose of the synchronization model implemented by this class is to allow the internal asynchronous/synchronous operations of the common language runtime to behave properly with different synchronization models.

That statement, along with what you know about multithreading, sheds light on the matter. Recall that GUI applications (WinForms, WPF) do not allow secondary threads to access controls directly but must delegate that access. We have already seen the Dispatcher object in the WPF example. For the console applications that did not use WPF, this restriction is not in place. These are the different synchronization models that are referred to. With that in mind, let’s dig deeper into the SynchronizationContext.
The SynchonizationContext is a type that provides a virtual post method, which takes a delegate to
be executed asynchronously. This provides a pattern for frameworks to appropriately handle asynchronous requests (dispatching for WPF/WinForms, directly executing for non-GUI applications, etc.). It provides a way to queue a unit of work to a context and keeps count of outstanding async operations.
As we discussed earlier, when a delegate is queued to run asynchronously, it is scheduled to run on a separate thread. This detail is handled by the .NET Core Runtime. This is typically done using the .NET Core Runtime managed thread pool but can be overridden with a custom implementation.
While this plumbing work can be manually managed through code, the async/await pattern does most of the heavy lifting. When an async method is awaited, it leverages the SynchronizationContext and TaskScheduler implementations of the target framework. For example, if you are using async/await in a WPF application, the WPF framework manages the dispatching of the delegate and calling back into the state machine when the awaited task completes in order to safely update controls.

The Role of ConfigureAwait
Now that you understand the SynchronizationContext a little better, it is time to cover the role of the ConfigureAwait() method. By default, awaiting a Task will result in a synchronization context being utilized. When developing GUI applications (WinForms, WPF), this is the behavior that you want. However, if you are writing non-GUI application code, the overhead of queuing the original context when not needed can potentially cause performance issues in your application.
To see this in action, update your top-level statements to the following:

Console.WriteLine(" Fun With Async ===>");
//Console.WriteLine(DoWork());
string message = await DoWorkAsync();

Console.WriteLine($"0 – {message}");

string message1 = await DoWorkAsync().ConfigureAwait(false); Console.WriteLine($"1 – {message1}");

The original code block is using the SynchronizationContext supplied by the framework (in this case, the .NET Core Runtime). It is equivalent to calling ConfigureAwait(true). The second example ignores the current context and scheduler.
Guidance from the .NET Core team suggests that when developing application code (WinForms, WPF, etc.), leave the default behavior in place. If you are writing nonapplication code (e.g., library code), then use ConfigureAwait(false). The one exception for this is ASP.NET Core (covered starting with Chapter 29).
ASP.NET Core does not create a custom SynchronizationContext; therefore, ConfigureAwait(false) does
not provide the benefit when using other frameworks.

Naming Conventions for Asynchronous Methods
Of course, you noticed the name change from DoWork() to DoWorkAsync(), but why the change? Let’s say that the new version of the method was still named DoWork(); however, the calling code has been implemented as so:

//Oops! No await keyword here! string message = DoWork();

Notice you did indeed mark the method with the async keyword, but you neglected to use the await keyword as a decorator before calling the DoWork() method call. At this point, you will have compiler errors, as the return value of DoWork() is a Task object, which you are attempting to assign directly to a string variable. Remember, the await token extracts the internal return value contained in the Task object. Since you have not used this token, you have a type mismatch.

■ Note an “awaitable” method is simply a method that returns a Task or Task.

Given that methods that return Task objects can now be called in a nonblocking manner via the async and await tokens, the recommendation is to name any method returning a Task with an Async suffix. In this way, developers who know the naming convention receive a visual reminder that the await keyword is required, if they intend to invoke the method within an asynchronous context.

■ Note event handlers for gui controls (such as a button Click handler) as well as action methods in MVC- style apps that use the async/await keywords do not follow this naming convention (by convention—pardon the redundancy!).

Async Methods That Don’t Return Data
Currently, your DoWorkAsync() method is returning a Task, which contains “real data” for the caller that will be obtained transparently via the await keyword. However, what if you want to build an asynchronous method that doesn’t return anything? While there are two ways to do this, there is really only one right way to do it. First, let’s look at the problems with defining an async void method.

Async Void Methods
The following is an example of an async method that uses void as the return type instead of Task:

static async void MethodReturningVoidAsync()
{
await Task.Run(() =>
{
/ Do some work here… / Thread.Sleep(4_000);
});
Console.WriteLine("Fire and forget void method completed");
}

If you were to call this method, it would run on its own without blocking the main thread. The following code will show the “Completed” message before the message from the MethodReturningVoidAsync() method:

MethodReturningVoidAsync(); Console.WriteLine("Completed"); Console.ReadLine();

While this might seem like a viable option for “fire and forget” scenarios, there is a larger problem. If the method throws an exception, it has nowhere to go except to the synchronization context of the calling method. Update the method to the following:

static async void MethodReturningVoidAsync()
{
await Task.Run(() =>
{
/ Do some work here… / Thread.Sleep(4_000);
throw new Exception("Something bad happened");
});
Console.WriteLine("Fire and forget void method completed");
}

To be safe, wrap the call to this method in a try-catch block, and run the program again:

try
{
MethodReturningVoidAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
}

Not only does the catch block not catch the exception, but the exception is placed on the threading execution context. So, while this might seem like a good idea for “fire and forget” scenarios, you better hope there isn’t an exception thrown in the async void method or your whole application might come crashing down.

Async Void Methods Using Task
The better way is to have your method use Task instead of void. Update the method to the following:

static async Task MethodReturningVoidTaskAsync()
{
await Task.Run(() =>
{
/ Do some work here… / Thread.Sleep(4_000);
});
Console.WriteLine("Void method completed");
}

If you call with method without the await keyword, the same result will happen as in the previous example:

MethodReturningVoidTaskAsync(); Console.WriteLine("Void method complete");

Update the MethodReturningVoidTaskAsync() to throw an exception:

static async Task MethodReturningVoidTaskAsync()
{
await Task.Run(() =>
{

});

/ Do some work here… / Thread.Sleep(4_000);
throw new Exception("Something bad happened");

Console.WriteLine("Void method completed");
}

Now wrap the call to this method in a try-catch block:

try
{

}

MethodReturningVoidTaskAsync();

catch (Exception ex)
{
Console.WriteLine(ex);
}

When you run the program and the exception is thrown, two interesting things happen. The first is that the whole program doesn’t crash, and the second is that the catch block doesn’t catch the exception. When an exception is thrown out of a Task/Task method, the exception is captured and placed on the Task object. When using await, the Exception (or AggregateException) is available to handle.

Update the calling code to await the method, and the catch block will now work as expected:

try
{

}

await MethodReturningVoidTaskAsync();

catch (Exception ex)
{
Console.WriteLine(ex);
}

To sum it up, you should avoid creating async void methods and use async Task methods. Whether or not you await them becomes a business decision, but either way, at least you won’t crash your app!

Async Methods with Multiple Awaits
It is completely permissible for a single async method to have multiple await contexts within its implementation. The following example shows this in action:

static async Task MultipleAwaits()
{
await Task.Run(() => { Thread.Sleep(2_000); }); Console.WriteLine("Done with first task!");

await Task.Run(() => { Thread.Sleep(2_000); }); Console.WriteLine("Done with second task!");

await Task.Run(() => { Thread.Sleep(2_000); }); Console.WriteLine("Done with third task!");
}

Again, here each task is not doing much more than suspending the current thread for a spell; however, any unit of work could be represented by these tasks (calling a web service, reading a database, etc.).
Another option is to not await each task but await them all together and return when all of the tasks are completed. This is a more likely scenario, where there are three things (check email, update server, download files) that must be completed in a batch, but can be done in parallel. Here is the code updated using the Task.WhenAll() method:

static async Task MultipleAwaitsAsync()
{
await Task.WhenAll(Task.Run(() =>
{
Thread.Sleep(2_000); Console.WriteLine("Done with first task!");
}), Task.Run(() =>
{
Thread.Sleep(1_000);
Console.WriteLine("Done with second task!");
}), Task.Run(() =>

{
Thread.Sleep(1_000); Console.WriteLine("Done with third task!");
}));
}

When you run the program now, you see that the three tasks fire in order of the lowest Sleep time.

Fun With Async ===> Done with second task! Done with third task! Done with first task! Completed

There is also a WhenAny(), which signals that one of the tasks have completed. The method returns the first task that completed. To demonstrate WhenAny(), change the last line of the MultipleAwaitsAsync to this:

await Task.WhenAny(Task.Run(() =>
{
Thread.Sleep(2_000); Console.WriteLine("Done with first task!");
}), Task.Run(() =>
{
Thread.Sleep(1_000);
Console.WriteLine("Done with second task!");
}), Task.Run(() =>
{
Thread.Sleep(1_000); Console.WriteLine("Done with third task!");
}));

When you do this, the output changes to this:

Fun With Async ===> Done with second task! Completed
Done with third task!
Done with first task!

Each of these methods also works with an array of tasks. To demonstrate this, create a new method named MultipleAwaitsTake2Async(). In this method, create a List, add the three tasks to it, and then call Task.WhenAll() or Task.WhenAny():

static async Task MultipleAwaitsTake2()
{
var tasks = new List(); tasks.Add(Task.Run(() =>
{

Thread.Sleep(2_000); Console.WriteLine("Done with first task!");
}));
tasks.Add(Task.Run(() =>
{
Thread.Sleep(1_000);
Console.WriteLine("Done with second task!");
}));
tasks.Add(Task.Run(() =>
{
Thread.Sleep(1_000); Console.WriteLine("Done with third task!");
}));
//await Task.WhenAny(tasks); await Task.WhenAll(tasks);
}

Calling Async Methods from Synchronous Methods
Each of the previous examples used the async keyword to return the thread to calling code while the async method executes. In review, you can use the await keyword only in a method marked async. What if you cannot (or do not want to) mark a method async?
Fortunately, there are ways to call async methods when in a synchronous context. Unfortunately, most of them are bad. The first option is to simply forego the await keyword, allowing the original thread to continue execution while the async method runs on a separate thread, never returning to the caller. This behaves like the previous example of calling async Task methods. Any values returned from the method are lost, and exceptions are swallowed.
This might fit your needs, but if it doesn’t, you have three choices. The first is to simply use the Result
property on the Task or the Wait() method on Task methods. If the method fails, any exceptions are wrapped in an AggregateException, potentially complicating error handling. You can also call
GetAwaiter().GetResult(). This behaves in the same way as the Wait() and Result calls, with the small difference of not wrapping exceptions into an AggregateException. While the GetAwaiter().GetResult() methods work on both methods with a return value and methods without a return value, they are marked in the documentation as “not for external use,” which means they might change or go away at some point in the future.
While those two options seem harmless replacements for using await in an async method, there is a more serious issue with using them. Calling either Wait(), Result, or GetAwaiter().GetResult() blocks the calling thread, processes the async method on another thread, then returns back to the calling thread, tying up two threads to get the work performed. Worse yet, each of these can cause deadlocks, especially if the calling thread is from the UI of the application.
To help discover and correct improper async/await code (and naming conventions), add the Microsoft.VisualStudio.Threading.Analyzers package to the project. This package adds analyzers that will provide compiler warnings when improper threading code is discovered, including bad naming conventions. To see this in action, add the following code to the top level statements:

= DoWorkAsyncO.Result;
_ = DoWorkAsyncO.GetAwaiter().GetResult();

This causes the following compiler warning:

VSTHRD002 Synchronously waiting on tasks or awaiters may cause deadlocks. Use await or JoinableTaskFactory.Run instead.

Not only is there a compiler warning, but the recommended solution is also provided! To use the JoinableTaskFactory class, you need to add the Microsoft.VisualStudio.Threading NuGet package and the following using statement to the top of the Program.cs file:

using Microsoft.VisualStudio.Threading;

The JoinableTaskFactory needs a JoinableTaskContext in the constructor:

JoinableTaskFactory joinableTaskFactory = new JoinableTaskFactory(new JoinableTaskContext());

With this in place, you can use the Run() method to safely execute an async method, such as the
DoWork() method, from a synchronous context:

string message2 = joinableTaskFactory.Run(async 0 => await DoWorkAsync());

As you know, the DoWork() method returns Task, and that value is indeed returned from the
Run() method. You can also call methods that just return Task, as follows:

joinableTaskFactory.Run(async () =>
{
await MethodReturningVoidTaskAsync(); await SomeOtherAsyncMethod();
});

■ Note even though the packages have Visualstudio in the name, the packages do not depend on Visual studio. they are .net packages that can be used with or without Visual studio installed.

Await in catch and finally Blocks
C# 6 introduced the ability to place await calls in catch and finally blocks. The method itself must be async
to do this. The following code example demonstrates the capability:

static async Task MethodWithTryCatch()
{
try
{
//Do some work return "Hello";
}
catch (Exception ex)
{
await LogTheErrors(); throw;
}

finally
{
await DoMagicCleanUp();
}
}

Generalized Async Return Types (New 7.0)
Prior to C# 7, the only return options for async methods were Task, Task, and void. C# 7 enables additional return types, if they follow the async pattern. One concrete example is ValueTask. To see this in action, create code like this:

static async ValueTask ReturnAnInt()
{
await Task.Delay(1_000); return 5;
}

The same rules apply for ValueTask as for Task, since ValueTask is just a Task for value types instead of forcing allocation of an object on the heap.

Local Functions with async/await (New 7.0)
Local functions were introduced in Chapter 4 and used throughout this book. They can also be beneficial for async methods. To demonstrate the benefit, you need to first see the problem. Add a new method named MethodWithProblems() and add the following code:

static async Task MethodWithProblems(int firstParam, int secondParam)
{
Console.WriteLine("Enter"); await Task.Run(() =>
{
//Call long running method Thread.Sleep(4_000); Console.WriteLine("First Complete");
//Call another long running method that fails because
//the second parameter is out of range Console.WriteLine("Something bad happened");
});
}

The scenario is that the second long-running task fails because of invalid input data. You can (and should) add checks to the beginning of the method, but since the entire method is asynchronous, there is no guarantee when the checks will be executed. It would be better for the checks to happen right away before the calling code moves on. In the following update, the checks are done in a synchronous manner, and then the private function is executed asynchronously:

static async Task MethodWithProblemsFixed(int firstParam, int secondParam)
{
Console.WriteLine(“Enter”);

if (secondParam < 0)
{
Console.WriteLine("Bad data"); return;
}

await actualImplementation();

async Task actualImplementation()
{
await Task.Run(() =>
{
//Call long running method Thread.Sleep(4_000); Console.WriteLine("First Complete");
//Call another long running method that fails because
//the second parameter is out of range Console.WriteLine("Something bad happened");
});
}
}

Cancelling async/await Operations
Cancellation is also possible with the async/await pattern and much simpler than with Parallel.ForEach. To demonstrate, we will use the same WPF project from earlier in the chapter. You can either reuse that project or add a new WPF app (.NET Core) to the solution and add the System.Drawing.Common package to the project by executing the following CLI commands:

dotnet new wpf -lang c# -n PictureHandlerWithAsyncAwait -o .\PictureHandlerWithAsyncAwait -f net6.0 dotnet sln .\Chapter15_AllProjects.sln add .\PictureHandlerWithAsyncAwait
dotnet add PictureHandlerWithAsyncAwait package System.Drawing.Common

If you are using Visual Studio, do this by right-clicking the solution name in Solution Explorer, selecting Add ➤ Project, and naming it PictureHandlerWithAsyncAwait. Make sure to set the new project as the startup project by right-clicking the new project name and selecting Set as StartUp Project from the context menu. Add the System.Drawing.Common NuGet package.

dotnet add PictureHandlerWithAsyncAwait package System.Drawing.Common

Replace the XAML to match the previous WPF project (DataParallelismWithForEach), except change the title to Picture Handler with Async/Await. Also make sure to update the main PropertyGroup in the project file to disable nullable reference types.


WinExe
net6.0-windows
enable
disable
true

In the MainWindow.xaml.cs file, make sure that the following using statements are in place:

using System.IO; using System.Windows; using System.Drawing;

Next, add a class-level variable for the CancellationToken and add the Cancel button event handler:

private CancellationTokenSource _cancelToken = null; private void cmdCancel_Click(object sender, EventArgs e)
{
_cancelToken.Cancel();
}

The process is the same as the previous example: get the picture directory, create the output directory, and get the picture files, rotate them, and save them to the new directory. Instead of using Parallel.
ForEach(), this new version will use async methods to do the work, and the method signatures accept a
CancellationToken as a parameter. Enter the following code:

private async void cmdProcess_Click(object sender, EventArgs e)
{
_cancelToken = new CancellationTokenSource(); var basePath = Directory.GetCurrentDirectory(); var pictureDirectory =
Path.Combine(basePath, "TestPictures"); var outputDirectory =
Path.Combine(basePath, "ModifiedPictures");
//Clear out any existing files
if (Directory.Exists(outputDirectory))
{
Directory.Delete(outputDirectory, true);
}
Directory.CreateDirectory(outputDirectory); string[] files = Directory.GetFiles(
pictureDirectory, "*.jpg", SearchOption.AllDirectories); try
{
foreach(string file in files)
{
try
{
await ProcessFileAsync(file, outputDirectory,_cancelToken.Token);
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex); throw;
}
}
}
catch (OperationCanceledException ex)

{
Console.WriteLine(ex); throw;
}
catch (Exception ex)
{
Console.WriteLine(ex); throw;
}
_cancelToken = null;
this.Title = "Processing complete";
}

After the initial setup, the code loops through the files and calls ProcessFileAsync() asynchronously for each file. The call to ProcessFileAsync() is wrapped in a try/catch block, and the CancellationToken is passed into the ProcessFile() method. If Cancel() is executed on the CancellationTokenSource (such as when the user clicks the Cancel button), an OperationCanceledException is thrown.

■ Note the try/catch code can be anywhere in the calling chain (as you shall soon see). Whether to place it at the first call or in the asynchronous method itself is a matter of pure preference and application needs.

The final method to add is the ProcessFileAsync() method.

private async Task ProcessFileAsync(string currentFile, string outputDirectory, CancellationToken token)
{
string filename = Path.GetFileName(currentFile); using (Bitmap bitmap = new Bitmap(currentFile))
{
try
{
await Task.Run(() =>
{
Dispatcher?.Invoke(() =>
{
this.Title = $"Processing {filename}";
});
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); bitmap.Save(Path.Combine(outputDirectory, filename));
}
,token);
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex); throw;
}
}
}

This method uses another overload of the Task.Run command, taking in the CancellationToken as a parameter. The Task.Run command is wrapped in a try/catch block (just like the calling code) in case the user clicks the Cancel button.

Cancelling async/await operations with WaitAsync() (New 10.0)
New in C# 10.0, async calls can be cancelled with a cancellation token and/or after a time limit has been reached by using the WaitAsync() method. The following examples (located in the FunWithCSharpAsync project in this chapter’s code) show all three variations of this new feature:

CancellationTokenSource tokenSource = new CancellationTokenSource();
= await DoWorkAsync().WaitAsync(TimeSpan.FromSeconds(10));
= await DoWorkAsync().WaitAsync(tokenSource.Token);
_ = await DoWorkAsync().WaitAsync(TimeSpan.FromSeconds(10), tokenSource.Token);

Cancelling async/await operations in Synchronous Calls
The Wait() method can also take a cancellation token when calling async methods from a non-async method. This can be used with or without a timeout specified. When used with a timeout, the timeout must be in milliseconds:

CancellationTokenSource tokenSource = new CancellationTokenSource(); MethodReturningTaskOfVoidAsync().Wait(tokenSource.Token); MethodReturningTaskOfVoidAsync().Wait(10000,tokenSource.Token);

You can also use the JoinableTaskFactory and the new WaitAsync() method when calling from synchronous code:

JoinableTaskFactory joinableTaskFactory2 = new JoinableTaskFactory(new JoinableTaskContext());
CancellationTokenSource tokenSource2 = new CancellationTokenSource(); joinableTaskFactory2.Run(async () =>
{
await MethodReturningVoidTaskAsync().WaitAsync(tokenSource.Token); await MethodReturningVoidTaskAsync().WaitAsync(TimeSpan.
FromSeconds(10),tokenSource.Token);
});

Asynchronous Streams (New 8.0)
New in C# 8.0, streams (covered in greater detail in Chapter 19) can be created and consumed asynchronously. A method that returns an asynchronous stream
•Is declared with the async modifier
•Returns an IAsyncEnumerable
•Contains yield return statements (covered in Chapter 8) to return successive elements in the asynchronous stream

Take the following example:

public static async IAsyncEnumerable GenerateSequence()
{
for (int i = 0; i < 20; i++)
{
await Task.Delay(100); yield return i;
}
}

The method is declared as async, returns an IAsyncEnumerable, and uses the yield return to return integers in from a sequence. To call this method, add the following to your calling code:

await foreach (var number in GenerateSequence())
{
Console.WriteLine(number);
}

The Parallel.ForEachAsync() Method (New 10.0)
New in C# 10, the Parallel class has a new ForEachAsync() method, which is (as the name suggests) async and also provides an async method for the body. Back in the DataParallelismWithForEach project, create a new async method named ProcessFilesAsync() and copy the code from the ProcessFiles() method.
Once the code is copied, replace the previous call to Parallel.ForEach() with await Parallel.
ForEachAsync(). The first two parameters (files, parOpts) are the same in both calls. The change is in the body, which takes two parameters, the currentFile and the cancellation token:

//Old code:
//Parallel.ForEach(files, parOpts, currentFile =>
//New code:
await Parallel.ForEachAsync(files, parOpts, async (currentFile, token) =>

The cancellation token that is passed into the body is the same cancellation token as the one in the ParallelOptions instance. This means we can check the token parameter for requested cancellation requests instead of the one on the ParallelOptions instance:

//Old code:
//parOpts.CancellationToken.ThrowIfCancellationRequested();
//New code: token.ThrowIfCancellationRequested();

The complete method is shown here:

private async Task ProcessFilesAsync()
{
// Load up all *.jpg files, and make a new folder for the modified data. var basePath = Directory.GetCurrentDirectory();
var pictureDirectory = Path.Combine(basePath, "TestPictures"); var outputDirectory = Path.Combine(basePath, "ModifiedPictures");
//Clear out any existing files

if (Directory.Exists(outputDirectory))
{
Directory.Delete(outputDirectory, true);
}
Directory.CreateDirectory(outputDirectory);
string[] files = Directory.GetFiles(pictureDirectory, "*.jpg", SearchOption. AllDirectories);
// Use ParallelOptions instance to store the CancellationToken. ParallelOptions parOpts = new ParallelOptions(); parOpts.CancellationToken = _cancelToken.Token; parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount; try
{
// Process the image data in a parallel manner!
await Parallel.ForEachAsync(files, parOpts, async (currentFile,token) =>
{
token.ThrowIfCancellationRequested();
string filename = Path.GetFileName(currentFile); Dispatcher?.Invoke(() =>
{
this.Title = $"Processing {filename} on thread {Environment. CurrentManagedThreadId}";
}
);
using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); bitmap.Save(Path.Combine(outputDirectory, filename));
}
Thread.Sleep(2000);
});
Dispatcher?.Invoke(() => this.Title = "Done!");
}
catch (OperationCanceledException ex)
{
Dispatcher?.Invoke(() => this.Title = ex.Message);
}
}

The final change is to call this new method when the button is clicked:

private void cmdProcess_Click(object sender, EventArgs e)
{
this.Title = $"Starting…"; ProcessFilesAsync().Wait();
}

Update the Book Reader App with async/await
Now that you understand the async/await pattern, let’s update the Book Reader app to use the HttpClient
instead of the WebClient. Create a new method named GetBookAsync().

async Task GetBookAsync()
{
HttpClient client = new HttpClient();
_theEBook = await client.GetStringAsync("http://www.gutenberg.org/files/98/98-0.txt"); Console.WriteLine("Download complete.");
GetStats();
}

After creating a new instance of the HttpClient class, the code calls the GetStringAsync() method to make an HTTP Get call to retrieve the text from the website. As you can see, the HttpClient provides a much more concise way to make HTTP calls. The HttpClient class will be explored in much more detail in the chapters that cover ASP.NET Core.

Wrapping Up async and await
This section contained a lot of examples; here are the key points of this section:
•Methods (as well as lambda expressions or anonymous methods) can be marked with the async keyword to enable the method to do work in a nonblocking manner.
•Methods (as well as lambda expressions or anonymous methods) marked with the
async keyword will run synchronously until the await keyword is encountered.
•A single async method can have multiple await contexts.
•When the await expression is encountered, the calling thread is suspended until the awaited task is complete. In the meantime, control is returned to the caller of the method.
•The await keyword will hide the returned Task object from view, appearing to directly return the underlying return value. Methods with no return value simply return void.
•Parameter checking and other error handling should be done in the main section of the method, with the actual async portion moved to a private function.
•For stack variables, the ValueTask is more efficient than the Task object, which might cause boxing and unboxing.
•As a naming convention, methods that are to be called asynchronously should be marked with the Async suffix.

Summary
This chapter began by examining the role of the System.Threading namespace. As you learned, when an application creates additional threads of execution, the result is that the program in question can carry out numerous tasks at (what appears to be) the same time. You also examined several manners in which you can protect thread-sensitive blocks of code to ensure that shared resources do not become unusable units of bogus data.
This chapter then examined some new models for working with multithreaded development introduced with .NET 4.0, specifically the Task Parallel Library and PLINQ. I wrapped things up by covering the role of the async and await keywords. As you have seen, these keywords are using many types of the TPL framework in the background; however, the compiler does most of the work to create the complex threading and synchronization code on your behalf.

发表评论