I noticed I was not getting logging messages in my app when some new async code was throwing an error. I have an app that kicks off a small number of indefinitely-running async tasks. Each of them runs their own while(true)
loop.
I have some error logging in a couple places, but I noticed that the exception propagation stops when it gets to the bounds of the infinite while
loop within the Task. Originally, my logging was only outside that loop, and while that particular Task stopped running, the error was not getting logged.
Here is a sample program that shows the same behavior - if I remove the while(true)
so that the Track()
method only runs once through, then the exception bubbles up to the RunAsync()
method, and the app crashes (which is what I would want). But with the while loop in there, it continues to run the loop for number 2, and number 1 dies a silent death.
I understand that unawaited Tasks can get their exceptions swallowed, but I believe I am awaiting everything that needs awaiting - please correct me if I'm wrong.
static void Main(string[] args)
{
Console.WriteLine("Starting up");
RunAsync().GetAwaiter().GetResult();
Console.WriteLine("Shutting down");
}
private static async Task RunAsync()
{
try
{
Console.WriteLine("In RunAsync");
List<int> trackers = new List<int> { 1, 2 };
await Task.WhenAll(trackers.Select(tracker => Track(tracker)));
}
catch (Exception e)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} => Error in RunAsync\n{e.StackTrace}");
throw;
}
}
private static async Task Track(int number)
{
Console.WriteLine($"Starting tracker for {number}");
while (true) // This causes the exception to not propagate up
{
Console.WriteLine($"In here for number {number}");
if (number == 1)
{
throw new Exception("Oops! Number 1 does not work");
}
await Task.Delay(1000);
}
}
Is there something I should be doing differently to handle the exceptions that may occur within that loop?
Here is the output with the loop in place
Starting up
In RunAsync
Starting tracker for 1
In here for number 1
Starting tracker for 2
In here for number 2
In here for number 2
In here for number 2
and here it is without the while loop
Starting up
In RunAsync
Starting tracker for 1
In here for number 1
Starting tracker for 2
In here for number 2
16:58:34.916 => Error in RunAsync
at Program.<Track>d__2.MoveNext() in Program.cs:line 48
--- End of stack trace from previous location where exception was thrown ---
CodePudding user response:
Task.WhenAll
will (asynchronously) wait for all tasks to complete. If one of them completes with an exception, then it will still wait for the other tasks to complete before (re)throwing the exception.
With the while (true)
in place, one of the tasks has completed with an exception and the other task executes indefinitely; since one of the tasks never completes, the WhenAll
also doesn't complete and doesn't (re)throw the exception. Without the while (true)
, both tasks complete quickly, one of them with an exception, and so the WhenAll
completes and (re)throws the exception.
If you want a WhenAll
that has fail-fast behavior, there is nothing built-in with that behavior, but there are a few solutions on Stack Overflow and elsewhere. Some of them get fancy with triggering a CancellationToken
in the fail-fast handling.