I'm encountering an issue where an AggregateException thrown from a .NET library is not caught by my try-catch block, causing my application to crash. This happens during a VPN disconnection scenario: I start the app while connected to a VPN, then disconnect, and the exception is thrown outside of my code's call stack. I’m using Polly's retry policy inside a Parallel.ForAsync loop, and although I catch exceptions in GetBatchAsync, the exception is still unhandled.
Repository GetBatchAsync() is called inside a Polly retryPolicy within Parallel.ForAsync.
PS. This is not related to the 'Enable Just My Code' setting.
Here is the relevant code:
await Parallel.ForAsync(0, totalBatchesCount, parallelOptions, async (i, cancellationToken) =>
{
//...
await retryPolicy.ExecuteAsync(async () =>
{
try
{
var batch = await _userRepository.GetBatchAsync(startId, endId);
//...
}
catch (Exception ex)
{
throw new Exception($"Batch failed for startId: {startId} and endId {endId}", ex);
}
});
});
Repository method:
public async Task<List<User>> GetBatchAsync(int startId, int endId)
{
try
{
await using var context = await _contextFactory.CreateDbContextAsync();
return await context.Users
.Where(u => u.UserId >= startId && u.UserId < endId)
.Include(u => u.Invoice)
.AsNoTracking()
.ToListAsync();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.ToString());
throw;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
throw;
}
}
Retry policy:
var retryPolicy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(POLLY_RETRY_COUNT, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, retryCount, context) =>
{
_logger.LogWarning($"Retry {retryCount} due to: {exception.Message}, stack trace: {exception.StackTrace}, inner: {exception?.InnerException?.Message}");
});
The exception thrown is as follows:
Unhandled exception. System.AggregateException: One or more errors occurred. (Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding)
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding
---> System.TimeoutException: The operation has timed out.
at MySql.Data.Common.StreamCreator.<>c.<GetTcpStreamAsync>b__8_1()
at System.Threading.CancellationTokenSource.Invoke(Delegate d, Object state, CancellationTokenSource source)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool)
at System.Threading.TimerQueue.FireNextTimers()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
I would appreciate any insights into why the exception thrown outside the lambda’s execution context isn’t caught and how I might properly handle it.