Handling Special Scenarios - Parallel Programming With C# and .NET - Fundamentals of Concurrency and Asynchrony Behind Fast-Paced Applications
Handling Special Scenarios - Parallel Programming With C# and .NET - Fundamentals of Concurrency and Asynchrony Behind Fast-Paced Applications
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2024
V. Sarcar, Parallel Programming with C# and .NET
https://doi.org/10.1007/979-8-8688-0488-5_2
Introduction to Exceptions
Let’s start with a simple class, called Product. This class has a method,
called CheckUser, to identify whether a user is a valid user. For simplicity,
let’s assume the following characteristic: Each valid user ID starts with
u. Otherwise, the CheckUser method throws an instance of
UnauthorizedAccessException.
Demonstration 1
Now let me hack the system to create this exception and handle it inside a try-
catch block. Here is a sample demonstration:
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 1/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
/// <returns>Confirming the valid user</returns>
/// <exception cref="UnauthorizedAccessException"> The
/// exception is thrown when the <paramref
/// name="userId" is an invalid ID </exception>
public static string CheckUser(string userId)
{
string msg;
if (userId.StartsWith("u"))
{
msg = $"{userId} is a valid user";
}
else
{
throw new UnauthorizedAccessException($"Id:
{userId} is invalid.");
}
return msg;
}
}
Output
There is no surprise that upon executing this program, you’ll see the following
output:
You are probably familiar with this type of program and there is nothing
new up to this point.
POINT TO NOTE
Visual Studio can still display this exception if you want. For example, in Visual
Studio’s Exception Settings (Ctrl+Alt+E), if you want your program to break when
a particular exception is thrown, you can enable the checkbox for that particular
exception. For example, I have the settings shown in Figure 2-1 for the common
language runtime exceptions.
Figure 2-1 Exception Settings for Common Language Runtime exceptions in Visual Studio
While executing the program, you will see the exception shown in Figure 2-2.
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 2/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Figure 2-2 Visual Studio displays the UnauthorizedAccessException while the program was
running
Note that the program has not crashed yet; you can click the Continue
button to resume the execution.
Demonstration 2
Now change the program a little bit and analyze the situation again. This time,
let’s invoke the CheckUser method through a task that is created inside the main
thread, as shown in the following program:
Output
Upon executing this program, you may see the following output:
You can see that this time the output does not show anything about the
exception. Why does this happen? Notice that this time the main thread
did not encounter the exception; it was encountered by the task called
validateUser that was created by this main thread. However, an unob-
served exception can cause problems at a later stage. So, you may be in-
terested in watching all the exceptions and decide to handle them as per
the priority. This is an important note for you to remember.
POINT TO NOTE
I’d like to remind you that many developers like to use Task.Run instead
of Task.Factory.StartNew. I also belong to that category. So, from this
time onward, I’ll be using Task.Run much more compared to
Task.Factory.StartNew. In this context, you may want to revisit Q&A
1.2 in Chapter 1. In the end, it’s up to you, so choose the approach that
you prefer. Once you finish Chapter 6 of this book, you can read an on-
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 3/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
How can I show you information about the exception? An obvious way is to
handle the exception inside the task itself. For example, I can replace the
following line:
However, you understand that inside the main thread, we can launch
many tasks, and if we code like this, there will be a lot of code
duplication.
Demonstration 3
// Previous code as it is
try
{
//Product.CheckUser("abc");
var validateUser = Task.Run(() => Product.CheckUser("abc"));
validateUser.Wait();
WriteLine("End");
}
// There is no change in the remaining code as well
Output
Once you execute the program again, you will see the following output:
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 4/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Caught error: One or more errors occurred. ('abc' is an invalid user.)
The output depicts that abc is an invalid user and it was the key information
that I wanted to display. If you put a breakpoint inside the catch block as shown
in Figure 2-3 and debug this code, you can see the values shown.
Figure 2-3 A partial snapshot from Visual Studio that shows an InnnerException
Q&A Session
catch (Exception e)
{
WriteLine($"Caught error: {e.Message}");
WriteLine($"Exception name: {e.GetType().Name}");
}
If you execute the application again, you will see the following output:
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 5/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
you are not careful enough, some of the exceptions can go unnoticed as
well. And this is an obvious problem because any exception can cause a
system to move into an inconsistent state. So, the question is, how can we
handle exceptions in task programming?
There are indeed different approaches, but the first thing that you should
note is that in the System namespace, there is a class called
AggregateException that inherits from the Exception class. Instead of using
any other exception class, you should prefer this class. This class is specially
designed to handle the exception handling scenarios when you deal with tasks.
Microsoft states the following (see https://learn.microsoft.com/en-
us/dotnet/standard/parallel-programming/exception-handling-
task-parallel-library):
To propagate all the exceptions back to the calling thread, the
Task infrastructure wraps them in an AggregateException in-
stance. The AggregateException exception has an
InnerExceptions property that can be enumerated to examine all
the original exceptions that were thrown, and handle (or not handle)
each one individually.
This is why you will see that AggregateException is used heavily in the
development code to consolidate multiple failures/errors in concurrent
environments, particularly, when we use Task Parallel Library (TPL)
and/or Parallel LINQ(PLINQ). This is why next time onward you will see
me using AggregateException inside the catch block as well.
Exception Management
In this section let’s consider the first category, i.e., how to handle the pos-
sible exceptions in one place.
Demonstration 4
Here is the complete program where I simulate the exceptional situations using
some simple code. In this program, in addition to the Product class, you will see
another class called Database. This class contains a method called StoreData
that can throw an InsufficientMemoryException when a user tries to store
more than 500 MB data. Here is the sample code for this:
class Database
{
/// <summary>
/// This method throws an exception when
/// requested size is greater than 500MB
/// </summary>
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 6/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
/// <param name="sizeInMB"> The requested size for
/// allocation in MB</param>
/// <returns> Confirming the storage</returns>
/// <exception cref="InsufficientMemoryException"> The
/// exception is thrown when the <paramref
/// name="sizeInMB" is greater than 500 </exception>
public static string StoreData(int sizeInMB)
{
string allocation;
if (sizeInMB > 500)
{
throw new InsufficientMemoryException($"Cannot
store {sizeInMB} MB data.");
}
else
{
// Some code for allocation
allocation = $"{sizeInMB} is allocated";
}
return allocation;
}
}
Now I create two different tasks inside the main thread and simulate the code
in a way that both tasks encounter exceptions. As discussed in the previous
section, it will be sufficient for you to pass through the inner exceptions and
display the error details. Go through the complete program now:
Output
Alternative Approaches
Alternative Approach 1
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 7/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Notice the previous catch block. You can see that I used ae.InnerExceptions
to display the errors in the output. Here is an alternative version where I flatten
the inner instances and then start traversing the exceptions as follows:
Alternative Approach 2
In the AggregateException class, you can see a method called Handle that
has the following form:
Using this method, you can invoke a handler on each exception contained in
an AggregateException. For example, let’s replace the foreach loop inside
the catch block in Demonstration 4 with a new block of code as follows (I have
shown all the different approaches that we have discussed so far using code
comments for easy comparison):
If you execute the program now, you will see the same output.
Q&A Session
Q2.2 I can see that you have thrown two different exceptions from
two different tasks. If both tasks throw the same type of exception,
how can I distinguish them?
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 8/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
This is easy. You can associate the task IDs with the exception’s Source property.
For example, if you replace the following lines:
Similarly, if multiple tasks throw the same exception, you can set the
Source property to identify the source of the error.
Demonstration 5
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 9/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Output
I assume that you have gotten the idea of how to handle multiple excep-
tions. This is OK and probably the most common approach. Next, I will
show you a mechanism where you can handle one part of the aggregate
exception in one place and the remaining part in another place. More
specifically, you propagate this remaining part of the exception up to the
hierarchy and handle it there. There is a reason for this: I want you to
show the effectiveness of the Handle method.
First, see the following code. This code fragment indicates that you catch the
probable set of exceptions but handle only one of them:
InsufficientMemoryException.
Demonstration 6
For example, in the upcoming program, the main thread calls the InvokeTasks
method that, in turn, creates and runs three new tasks as follows:
The first two tasks are already shown in the previous demonstration. You
know that these tasks will raise exceptions. The third task, named task3, is
added for the sake of discussion so that you do not assume that you need to
handle an equal number of tasks in each location.
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 10/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
You’ll see that I catch all the possible sets of exceptions inside
InvokeTasks but handle only one of them:
InsufficientMemoryException. As a result, the remaining exceptions
will be passed up to the calling hierarchy. So, I have handled them inside
the Main method aka main thread. Let’s go through the complete program
now.
NoteI remind you that I have heavily used top-level statements and en-
abled implicit using statements for the C# projects in this book. These
features came in C# 9.0 and C# 10.0, respectively. This is why you do not
see a method named Main in this program.
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 11/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Output
Final Suggestion
Understanding Cancellations
If you want to open a fixed deposit (FD) in India, banks give you two op-
tions: callable FD and non-callable FD. Usually, the interest rates for non-
callable FDs are slightly higher than their counterpart. This is because
you are not allowed to cancel the FD and withdraw the amount before the
specified date. But you know that in a crisis, a customer may need to can-
cel the FD. So, if the customer does not have sufficient backup, he may opt
for a callable FD. Similar situations are observed in programming as well.
You may need to work with several tasks, and while those tasks are run-
ning, you may need to cancel one or more of them due to various reasons.
Some of the examples include terminating a long-running process or re-
leasing a critical resource. This is why you may like to work with can-
cellable tasks. Let’s see how to cancel a task.
Obviously, using the var keyword, you can see a slight variation as follows:
Next, you pass this token to the intended task. Earlier, you saw (in Figure 1-
1 in Chapter 1) that the Task constructor has several overloaded versions. Some
of them accept a CancellationToken instance as a method parameter. Here is
an example:
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 12/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Similarly, the Run method of the Task class has similar overloads.
In the upcoming demonstration, I created a task that can keep printing the
numbers from 0 to 99. Nowadays computer processors are very fast. So, this task
can finish its execution very fast. To prevent this, I impose a short sleep after it
prints a number. I also opt for the support for cancellation while it executes. So, I
create a CancellationToken instance, called token, and pass it as follows:
var printTask=Task.Run
(
() =>
{
// A loop that runs 100 times
for (int i = 0; i < 100 ; i++)
{
// Some other code is skipped here
WriteLine($"{i}");
// Imposing the sleep to make some delay
Thread.Sleep(500);
}
}, token
);
However, you must remember the following guidelines from Microsoft (see
https://learn.microsoft.com/en-us/dotnet/standard/parallel-
programming/how-to-cancel-a-task-and-its-children):
The calling thread does not forcibly end the task; it only signals
that cancellation is requested. If the task is already running, it is up
to the user delegate to notice the request and respond appropriately.
NoteThis prior message indicates that it is possible that by the time a call-
ing thread raises a cancellation request, the running task finishes its exe-
cution. So, if you want to cancel a running task, you should raise the can-
cellation request as soon as possible.
Let me show you some cancellation techniques.
Initial Approach
In the first approach, you can use an if condition to evaluate whether the
cancellation request is raised. If so, you can do something before you can-
cel the task. For example, you can introduce a message saying that this
task is going to cancel itself (and clean up the resources, if required), and
then you can put a break or return statement. Probably, most of us are
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 13/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Demonstration 7
The following demonstration shows you a sample where I added a new code
block in the previous code segment and made it bold for your reference:
POINT TO NOTE
This program uses the following line:
while (!someTask.IsCompleted) { }
Microsoft suggests you avoid this kind of polling in the production code
as it is very inefficient (see https://learn.microsoft.com/en-
us/dotnet/standard/parallel-programming/exception-handling-
task-parallel-library). So, a better solution can be made with
printTask.Wait();. But if I use the Wait() method, I need to guard the
possible exceptions using try-catch blocks. To avoid this, I have used
this line of code in this program and the next few demonstrations be-
cause I wanted to focus purely on cancellations, instead of mixing it up
with exceptions.
Output
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 14/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Here is some sample output:
Q&A Session
Q2.3 In the previous demonstration, you canceled the task, but in the
output, the final status of the task was displayed as RanToCompletion,
instead of Canceled. Is this a bug?
No. Let’s see what Microsoft says about it. The online documentation states
the following (see https://learn.microsoft.com/en-
us/dotnet/standard/parallel-programming/task-cancellation):
In the Task classes, cancellation involves cooperation between the
user delegate, which represents a cancelable operation, and the code
that requested the cancellation. A successful cancellation involves
the requesting code calling the CancellationTokenSource.Cancel
method and the user delegate terminating the operation in a timely
manner. You can terminate the operation by using one of these op-
tions:
The first bullet point is easy to understand and justifies the final status
of printTask in Demonstration 7. In the next section, I’ll show you the
other approach where you will notice that the final status is Canceled.
Alternative Approach
if (token.IsCancellationRequested)
{
WriteLine("Cancelling the print activity.");
throw new OperationCanceledException(token);
}
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 15/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Demonstration 8
It is time for another demonstration. You can update the task definition in
Demonstration 7 as follows:
Output
Here is a sample output. Notice that in Demonstration 7, the final status of the
task was RanToCompletion, but in this demonstration, it appears as Canceled.
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 16/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
This implies that you can use the ThrowIfCancellationRequested
method for the following two things:
This is a common and widely used approach. Normally, I prefer to use this
approach. If you like to do some cleanups before this call, you can write
something like the following as well:
if (token.IsCancellationRequested)
{
// Do some cleanups, if required
token.ThrowIfCancellationRequested();
}
Q&A Session
Q2.4 In Demonstration 7, you simply did a soft exit and got the final
task status as RanToCompletion, whereas in Demonstration 8, the fi-
nal task status was canceled. I understand that this is a design deci-
sion, but I’d like to know your thoughts on them.
OperationCanceledException(CancellationToken): Initializes a
new instance with a cancellation token. (You have already seen this in
Demonstration 8).
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 17/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
To experiment with this, let’s create a new token, say token2, and update the
if block in Demonstration 8 as follows:
// Approach-2
if (token.IsCancellationRequested)
{
WriteLine("Cancelling the print activity.");
// Do some cleanups, if required
throw new OperationCanceledException(token2);
}
Execute the program again. Notice that the final status appears as Faulted,
but not Canceled. Here is a sample for you:
Q&A Session
Q2.5 Why does the output show the final status Faulted instead of
Canceled?
Using Register
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 18/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
You can subscribe to an event notification. For example, in the following code, I
register a delegate that will be called when the token is canceled:
token.Register(
() =>
{
WriteLine("Cancelling the print activity.
[Using event subscription]");
// Do something else, if you want
}
);
Using WaitHandle.WaitOne
Let me show you one more approach that is relatively complicated compared to
the previous one. However, this will also give you an idea about how to monitor
task cancellation. The documentation describes WaitHandle’s WaitOne method
as follows (see https://learn.microsoft.com/en-
us/dotnet/api/system.threading.waithandle.waitone?view=net-
8.0):
Blocks the current thread until the current WaitHandle receives a
signal.
Task.Run(
() =>
{
token.WaitHandle.WaitOne();
WriteLine("Cancelling the print activity.
[Using WaitHandle]");
// Do something else, if you want
}
);
Demonstration 9
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 19/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
var printTask = Task.Run
(
() =>
{
// A loop that runs 100 times
for (int i = 0; i < 100; i++)
{
// Approach-3
token.ThrowIfCancellationRequested();
WriteLine($"{i}");
// Imposing the sleep to make some delay
Thread.Sleep(500);
}
}, token
);
Task.Run(
() =>
{
token.WaitHandle.WaitOne();
WriteLine("Cancelling the print activity.[Using
WaitHandle]");
// Do something else, if you want
}
);
WriteLine("Enter c to cancel the task.");
char ch = ReadKey().KeyChar;
if (ch.Equals('c'))
{
WriteLine("\nTask cancellation requested.");
tokenSource.Cancel();
}
// Wait till the task finishes the execution
while (!printTask.IsCompleted) { }
WriteLine($"The final status of printTask is: {printTask.Status}");
WriteLine("End of the main thread.");
Output
I assume you understand task cancellations now. I’d like to remind you
that Demonstration 8 in Chapter 1 showed you how to cancel a child task
to answer Q1.8. That is why I won’t repeat the discussion here.
Q&A Session
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 20/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Good question. You may need to work with several tokens. The upcoming
demonstration will give you the idea.
The idea is that you can cause a cancellation using either normalCancel-
lation or unexpectedCancellation.
Demonstration 10
In the following program, a user can trigger a normal cancellation. But you can
also observe an unexpected/emergency cancellation as well. To mimic an
emergency cancellation, I rely on a generated random number. If the number is 5,
the unexpected cancellation will be triggered. Here is the complete program to
demonstrate the idea:
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 21/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
// Do something else, if you want
}
);
var compositeToken = CancellationTokenSource.CreateLinkedTokenSource(
tokenNormal,
tokenUnexpected
);
var printTask = Task.Run
(
() =>
{
// A loop that runs 100 times
for (int i = 0; i < 100; i++)
{
compositeToken.Token.ThrowIfCancellationRequested();
WriteLine($"{i}");
// Imposing sleep to make some delay
Thread.Sleep(500);
}
}, compositeToken.Token
);
int random = new Random().Next(1, 6);
// A dummy logic to mimic an emergency cancellation
if (random == 5)
unexpectedCancellation.Cancel();
else
{
WriteLine("Enter 'c' for a normal cancellation ");
char ch = ReadKey().KeyChar;
if (ch.Equals('c'))
{
WriteLine("\nTask cancellation requested.");
normalCancellation.Cancel();
}
}
// Wait till the task finishes the execution
while (!printTask.IsCompleted) { }
WriteLine($"The final status of printTask is: {printTask.Status}");
WriteLine("End of the main thread.");
Output
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 22/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
End of the main thread.
If you are a Visual Studio user and write programs that deal with multiple
cancellation requests, I want you to remember the following note from Microsoft
(see https://learn.microsoft.com/en-
us/dotnet/standard/threading/how-to-listen-for-multiple-
cancellation-requests):
When “Just My Code” is enabled, Visual Studio in some cases will
break on the line that throws the exception and display an error mes-
sage that says “exception not handled by user code.” This error is be-
nign. You can press F5 to continue from it.
In Chapter 1, you learned that there are three possible final states of a
task: RanToCompletion, Canceled, and Faulted. In this chapter, you
learned about cancellations and exceptions (particularly, we talked about
AggregateException and OperationCanceledException). However, up
until now, I discussed exceptions and cancellations in separate sections.
This is why typical try-catch blocks were absent when I discussed can-
cellations. But typical software must be prepared for exceptions and can-
cellations. So, it is time to consolidate all this knowledge in a single place.
Let’s analyze some case studies.
Demonstration 11
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 23/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
WriteLine("Press c to cancel within 5 sec.");
// Assuming the task will take 5 seconds.
// So, after every second, we'll increase
// the progress by 20%
for (int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(1000);
progressBar += 20;
WriteLine($"Progress:{progressBar}%");
}
return "The money transfer is completed.";
}, token);
var input = ReadKey().KeyChar;
if (input.Equals('c'))
{
WriteLine("\nCancellation is requested.");
cts.Cancel();
}
try
{
transferMoney.Wait();
}
catch (AggregateException ae)
{
ae.Handle(e =>
{
WriteLine($"Caught error: {e.Message}");
return true;
});
}
catch (OperationCanceledException oce)
{
WriteLine($"Caught error due to cancellation: {oce.Message}");
}
if (transferMoney.Status == TaskStatus.RanToCompletion)
{
WriteLine(transferMoney.Result);
}
WriteLine($"Current status: {transferMoney.Status}");
WriteLine("Thank you, visit again!");
Output
Let me show you some possible outputs. Notice the important lines in
bold in these outputs.
Case 1: In this case, you do not cancel the task. Here is some sample output:
Case 2: Here is some sample output from the program when you try to cancel
the task:
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 24/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Let’s replace the try block in the previous program with the following one:
Let’s execute this modified program and analyze the previous cases again.
First, if you do not cancel the task, you will not observe any change in the output.
However, if you initiate a cancellation, there are some differences. Let me show you
some sample output:
You can see that this time the catch block for AggregateException could
not catch OperationCanceledException; this is why I used two different
catch blocks in this program.
Q&A Session
Q2.7 Why does the previous output show the final status Running in-
stead of Canceled?
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 25/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
// There is no change in the remaining code.
POINT TO NOTE
When you examine this in detail, you’ll see that Wait() can throw only
AggregateException, whereas Wait(CancellationToken cancella-
tionToken) is cancellable and can raise OperationCanceledException.
If interested, you can see a discussion of this topic at
https://stackoverflow.com/questions/77833724/why-the-catch-
block-of-aggregateexception-was-not-sufficient-to-handle-
cancellat/77833858#77833858.
Using TaskCompletionSource
More specifically, behind the scenes, using this class, you get a “slave”
task that you can manually drive when the operation finishes (or faults).
It can be ideal for such a type of I/O-bound work where you reap all the
benefits of using a task without blocking the calling thread.
Now the question is: how do you use this class? First, you need to instantiate
it. Once you instantiate this class, you will get some built-in methods that can
serve your purpose. Figure 2-4 shows the details of this class in Visual Studio.
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 26/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Figure 2-4 confirms that the method names start either with Set or
with TrySet. The first category returns void, and the second category
returns bool.
When you call any of these methods, the task moves into any of the
final states: RanToCompletion, Faulted, or Canceled.
You should call the first category (i.e., where the method names
start with the word Set) exactly once; otherwise, you’ll see exceptions.
To illustrate the previous bullet point, let me give you an example: in the
upcoming program, there is a custom class called Job. I created an instance of it,
called job, and used it in the task definition as follows (notice that the
SetResult method is called exactly once):
Now if you uncomment the commented-out line in the previous code and wait
for this task to finish (for example, using the line backgroundTask.Wait();),
you will see the following exception in the final output:
Unhandled exception. System.AggregateException: One or more errors occurred. (An attempt was mad
// The remaining details are not shown
To avoid this kind of error, I prefer to use the TrySetResult method in-
stead of the SetResult method. The reason is obvious: it returns a
Boolean, i.e., either true or false.
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 27/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Output
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 28/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
Here is some sample output:
Q&A Session
Q2.8 I can see that you have used a property and a constructor to in-
stantiate a Job instance. Is there any specific reason behind this
design?
Yes. You can indeed opt for any of them. But notice that I have used the init
accessor in this example. About this accessor, Microsoft says the following (see
https://learn.microsoft.com/en-us/dotnet/csharp/language-
reference/keywords/init):
In C# 9 and later, the init keyword defines an accessor method in
a property or indexer. An init-only setter assigns a value to the prop-
erty or the indexer element only during object construction. This en-
forces immutability, so that once the object is initialized, it can't be
changed again.
This is why the job number cannot be changed in this program, but its ex-
ecutor can be reassigned. Fixing the job number for a particular job, I
promote immutability. In a multithread environment, promoting im-
mutability is a good idea.
Immutable types are thread-safe. They prevent nasty bugs from implicit
dependencies, destructive updates, and state mutations. Therefore, in a
multithreaded environment, they make your programming life easy.
In addition, immutable types can help you avoid side effects and tempo-
ral coupling as well. Indeed, I am not focusing on functional program-
ming in this book. So, for the simple demonstrations, I do not care about
them. I simply wanted you to keep these notes in your mind using this
program.
Exercises
REMINDER
As said before for Chapter 1’s exercises, for all code examples, the
Implicit Global Usings setting was enabled in Visual Studio. This is why
you will not see me mentioning some other namespaces that were avail-
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 29/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
able by default. You can safely assume that all other necessary name-
spaces are available for these code segments. The same comment applies
to all exercises in this book as well.
E2.1 If you execute the following code, can you predict the output?
E2.2 If you execute the following code, can you predict the output?
E2.3 If you execute the following code, can you predict the output?
E2.4 If you execute the following code, can you predict the output?
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 30/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
{
var task1 = Task.Run( () => throw new
InvalidDataException("invalid data"));
var task2 = Task.Run(() => throw new
OutOfMemoryException("insufficient memory"));
task1.Wait();
task2.Wait();
WriteLine("End");
}
catch (AggregateException ae)
{
ae.Handle(e =>
{
if (e is InvalidDataException |
e is OutOfMemoryException )
{
WriteLine($"Caught error: {e.Message}");
return true;
}
return false;
}
);
}
E2.5 If you execute the following code, can you predict the output?
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 31/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
return true;
}
else
{
return false;
}
}
);
}
}
E2.6 Predict the output when you replace the following line:
Task.WaitAll(task1, task2);
task1.Wait();
task2.Wait();
E2.7 If you execute the following code, can you predict the output?
E2.8 If you execute the following code, can you predict the output?
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 32/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
}
// Waiting for the state change now
while (someTask.Status == TaskStatus.Running)
{
Thread.Sleep(10);
}
WriteLine($"SomeTask's status: {someTask.Status}");
WriteLine("The application ends here.");
}
catch (AggregateException ae)
{
ae.Handle(e =>
{
if (e is InvalidOperationException )
{
WriteLine($"Caught error: {e.Message} Source=
{e.Source}");
return true;
}
return false;
}
);
}
E2.9 If you execute the following code, can you predict the output?
E2.10 In the previous exercise, if you replace the following code segment:
if (token.IsCancellationRequested)
{
WriteLine("Cancelling the print activity.");
return;
}
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 33/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
with the following line:
token.ThrowIfCancellationRequested();
E2.11 The following program creates a parent task and a nested task. It also
allows you to cancel the nested task if you enter C quickly. Check whether you can
predict the output.
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 34/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
WriteLine("\nTask cancellation requested.");
tokenSource.Cancel();
}
WriteLine($"Status: {parent.Result}");
WriteLine("End of the main thread.");
E2.12 If you execute the following code, can you predict the output?
E2.13 In Chapter 1 you solved exercise 1.6. Since you learned about
implementing exception and cancellation scenarios, can you solve
that exercise considering these scenarios?
Summary
This chapter continued the discussion of task programming, but this time the
focus was on handling various special scenarios with different examples and case
studies. In brief, it answered the following questions:
Solutions to Exercises
E2.1
Exercise 2.1
End
Additional Note: You do not observe the exception because the main
thread did not encounter the exception; it was encountered by the task
that was created by this main thread. I covered this topic using
Demonstration 2 and Demonstration 3.
E2.2
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 35/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
This time you’ll see the exception name. You may note that instead of seeing
System.DivideByZeroException, you’ll see System.AggregateException
as follows:
Exercise 2.2
Encountered with System.AggregateException
End
E2.3
Exercise 2.3
Caught error: invalid data
Caught error: insufficient memory
E2.4
Exercise 2.4
Caught error: invalid data
E2.5
E2.6
The program produces the following output (see the explanation of E2.4 if
required):
E2.7
Exercise 2.7
End
You may be wondering why are you not seeing the task’s exception(s) in the
output. This is because when you use the WaitAny method, the task’s exception
does not propagate to the AggregateException. I encourage you to read
Stephen Clearly’s blog post at
https://blog.stephencleary.com/2014/10/a-tour-of-task-part-5-
wait.html that summarizes the difference among WaitAny, Wait, and WaitAll
as follows:
The semantics of WaitAny are a bit different than WaitAll and
Wait: WaitAny merely waits for the first task to complete. It will not
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 36/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
E2.8
Exercise 2.8
SomeTask's status: Faulted
The application ends here.
However, you may also notice that the program is stuck executing. Why?
Notice that when you evaluate the following condition, the task may not be in the
Running state (for example, it may be in the WaitingForActivation state):
// Allowing some time so that the task can get up and running
while (someTask.Status != TaskStatus.Running)
{
Thread.Sleep(10);
}
But inside the loop when you invoke the Sleep method, by that time, the
task may transition from the Running state to one of the terminating
states. As a result, the program will stuck here.
This is why to get a consistent output, you’d like to replace the following
segment:
NoteI already mentioned that you should avoid this kind of polling in the
production code as it is inefficient.
E2.9
E2.10
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 37/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
This time the final task status should appear as Canceled. Here is some sample
output:
E2.11
You already know that this program creates a parent task and a nested task. It
also allows you to cancel the nested task if you enter C quickly. As a result, you
may see different output. For example, if you do not intend to cancel the nested
task and press the Enter key at the end, you can see the following output:
Exercise 2.11
Enter c to cancel the nested task.
The parent task has started.
The nested task has started.
The nested task prints:0
The nested task prints:1
The nested task prints:2
The nested task prints:3
The nested task prints:4
The nested task prints:5
The nested task prints:6
The nested task prints:7
The nested task prints:8
The nested task prints:9
Status: Parent task: completed. Additional info: The nested task has finished too.
End of the main thread.
On the other hand, depending on the time of cancellation, you can get
different outputs. For example, if you press C almost at the beginning of the
application, you will see the following:
Exercise 2.11
Enter c to cancel the nested task.
c
Task cancellation requested.
Error: The operation was canceled.
Status: The cancellation request is raised before the start of the parent task.
End of the main thread.
Exercise 2.11
Enter c to cancel the nested task.
The parent task has started.
The nested task has started.
The nested task prints:0
The nested task prints:1
The nested task prints:2
The nested task prints:3
c
Task cancellation requested.
Caught error: A task was canceled.
Status: Parent task: completed. Additional info: n/a.
End of the main thread.
E2.12
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 38/39
2024/10/31 晚上9:06 2. Handling Special Scenarios | Parallel Programming with C# and .NET: Fundamentals of Concurrency and Asynchrony B…
You should see the following output:
[Clue: Notice that task1 updates the initial value to 11, but task2 further
sets it as 11*10=110. The sleep statements are placed to preserve the or-
der of evaluation.]
E2.13
https://learning.oreilly.com/library/view/parallel-programming-with/9798868804885/html/617342_1_En_2_Chapter.xhtml 39/39