Reputation: 427
I have below C# code that runs periodically, e.g., every 1 hour. Every time it runs, it tries to make some remote calls in parallel. When any of these remote calls throws an exception, I create only one ticket, or update the previous ticket. And in the finally block, resolve the existing previous ticket if there is no exception this time. Essentially I want to make sure that no matter how many remote calls fail, I only have one ticket to investigate. And when next time all calls succeed, the ticket gets resolved automatically.
However, when the remote calls all succeed in the try block, the call in finally block tries to resolve the ticket, but ran into HTTP 412 Precondition Failed, meaning that the ticket I got in try block was somehow updated before the finally block. If it's updated in the same thread, I wouldn't try to resolve it.
I learnt from this post that Task.WhenAll
will wait for all tasks to complete even in the presence of failures (faulted or canceled tasks). In case where multiple tasks throw exceptions, would the catch block run once or more? How about the finally block?
Ticket ticket = null;
try
{
// Query to get the existing ticket if there is any.
ticket = await QueryExistingTicketAsync();
// Make a lot of remote calls in parallel
var myTasks = new List<Task>();
myTasks.Add(RemoteCallAsync("call1"));
myTasks.Add(RemoteCallAsync("call2"));
myTasks.Add(RemoteCallAsync("call3"));
// ... add more Tasks for RemoteCallAsync()
await Task.WhenAll(myTasks);
}
catch (Exception ex)
{
if (ticket != null)
{
ticket.ReChecked = true;
ticket.LastCheckTime = DateTimeOffset.Now;
// If the previous ticket exists, meaning the last run failed as well,
// update the timestamp on that ticket.
ticket = await UpdateExistingTicketAsync(ticket);
}
else
{
// If the previous ticket does not exist yet,
// create one ticket for investigation, this ticket.ReChecked will be true
ticket = await CreateNewTicketAsync();
}
}
finally
{
// Resolve the previous ticket if it was not created/updated in catch block
if (ticket != null && !ticket.ReChecked)
{
ticket.Status = "Resolved";
await UpdateExistingTicketAsync(ticket);
}
}
Upvotes: 0
Views: 1208
Reputation: 43525
The catch
and the finally
blocks are running once, but can run both. The finally
block is always running, while the catch
block runs only in case of an exception, before the finally
block.
There is another issue with your code though.
try
{
Ticket ticket = await QueryExistingTicketAsync();
// ...
}
catch (Exception ex)
{
if (ticket != null)
{
The ticket
you create on try
and the ticket
you update on catch
is not the same ticket. The ticket
declared on try
has local scope, and so it's not visible outside this block. You may have declared another ticket
variable before the try-catch-finally block, but even in this case the compiler would complain for the double variable declaration. So in any case the code you provided should not be able to compile.
Upvotes: 1
Reputation: 10563
Short answer: even if multiple tasks throw exceptions, there will be only one exception thrown by await Task.WaitAll
and thus the catch
block will execute only once. The finally
block always executes once per entry to the try
block. Long answer follows.
Awaiting a Task.WaitAll
, if any wrapped Task
s throw an exception, throws an AggregateException
containing exceptions from all of the Task
s. So assuming you've reached the await Task.WhenAll(tasks)
line without exceptions, here's what happens:
Task.WhenAll
will wait for all the Task
s to complete, regardless of whether they fault or not.Task
s completed successfully, the Task.WhenAll
completes successfully, execution resumes at the await
, end of the try
block is reached, the finally
block executes.Task
s finished with an exception, Task.WhenAll
wraps all of them into an AggregateException
and completes in a faulted state. The execution resumes at the await
and the aforementioned AggregateException
is thrown. The catch
block is executed and then the finally
block.So, assuming that one thread enters the try
block, in both cases when the execution resumes at the await
only one thread continues the execution. The only way for you to enter the catch
or finally
blocks twice is to execute the method twice.
The await
statement does a lot of magic under the hood, but it never causes your execution to continue on multiple threads. One thread await
s, only one thread picks it up and continues.
Upvotes: 2