Run the following code:
using static System.Console;
WriteLine("Handling cancellations and exceptions.");
CancellationTokenSource cts = new();
CancellationToken token = cts.Token;
var transferMoney = Task<string>.Factory.StartNew(
() =>
{
WriteLine($"Initiating the money transfer.");
int progressBar = 0;
WriteLine("Press c to cancel withing 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 "Money transfer is completed.";
}, token);
var input = ReadKey().KeyChar;
if (input.Equals('c'))
{
WriteLine("\nCancellation is requested.");
cts.Cancel();
}
try
{
transferMoney.Wait();
WriteLine(transferMoney.Result);
}
catch (AggregateException ae)
{
ae.Handle(e =>
{
WriteLine($"Caught error : {e.Message}");
return true;
});
}
catch (OperationCanceledException oce)
{
WriteLine($"Caught error due to cancellation :{oce.Message}");
}
WriteLine($"Payment processing status: {transferMoney.Status}");
WriteLine("Thank you, visit again!");
Here is a sample output( expected behavior- no doubt):
Handling cancellations and exceptions.
Initiating the money transfer.
Press c to cancel withing 5 sec.
Progress:20%
Progress:40%
Progress:60%
c
Cancellation is requested.
Progress:80%
Caught error : A task was canceled.
Payment processing status: Canceled
Thank you, visit again!
Now update the try block with any of the following statement:
try
{
//transferMoney.Wait();
transferMoney.Wait(token);
//await transferMoney; //Same observation
// There is no change in the remaining code
And run the code again. Here is a sample output.
Handling cancellations and exceptions.
Initiating the money transfer.
Press c to cancel withing 5 sec.
Progress:20%
Progress:40%
c
Cancellation is requested.
**Caught error due to cancellation :The operation was canceled.**
Payment processing status: Running
Thank you, visit again!
Notice that this time the catch block for AggregateException was not sufficient to handle OperationCanceledException. I'd like to know the reason behind this. Can you please share your thoughts-where am I missing? [Additional note: I understand that in earlier case, wait() can throw only AggregateException but this overloaded version of Wait(i.e. Wait(token)) can throw OperationCancelledException as well. ]
From the
Task.Wait(CancellationToken)docs:And
Your wait is cancelled in the second case so you get
OperationCanceledException, i.e. the method can understand that the cancellation (of monitored token) has happened, not some random error (which will go to the "task completes" case and will result inAggregateException) .OperationCanceledExceptiondoes not inherit from theAggregateExceptionso thecatch(AggregateException)can't handle it.OperationCanceledExceptionis prioritized over the any other exception if any happened during the task execution. From theTask.Waitsource code: