I'm not sure if I'm stopping a Parallel.ForEach
loop as I intend to do.
So let me outline the problem.
The loop uses a database driver with limited available connections and it is required to keep track of the open connections, so the database driver doesn't throw an exception. The issue is that keeping track of open connections has been implemented manually (this should be refactored - writing a wrapper or using AutoResetEvent
but there are some other things that need to be taken care of first). So I need to keep track of the open connections and especially I have to handle the case of an exception:
Parallel.ForEach(hugeLists, parallelOptions, currentList => {
WaitForDatabaseConnection();
try {
Interlocked.Increment(ref numOfOpenConnections);
DoDatabaseCallAndInsertions();
} catch (Exception ex) {
// logging
throw;
} finally {
Interlocked.Decrement(ref numOfOpenConnections);
}
}
This is the simplified version of the loop without cancellation. To improve the performance in case of an Exception the loop should be cancelled as soon as possible when an Exception is thrown. If one thing fails the loop should stop.
How can I achieve that making sure that numOfOpenConnections
is being updated correctly?
What I have tried so far (is this behaving like I want it to or am I missing something?):
Parallel.ForEach(hugeLists, parallelOptions, (currentList, parallelLoopState) => {
parallelOptions.CancellationToken.ThrowIfCancellationRequested();
WaitForDatabaseConnection();
try {
Interlocked.Increment(ref numOfOpenConnections);
DoDatabaseCallAndInsertions();
} catch (Exception ex) {
// logging
cancellationTokenSource.Cancel();
parallelLoopState.Stop();
throw; // still want to preserve the original exception information
} finally {
Interlocked.Decrement(ref numOfOpenConnections);
}
}
I could wrap this code in a try - catch construct and catch AggregateException
.
parallelLoopState.Stop
and use aCancellationTokenSource
? Does it make the termination of the parallel loop any faster than just letting the method terminate by itself? AFAIK an unhandled exception in the lambda will make the parallel loop to terminate as soon as all currently running lambdas have completed. – PiezoelectricityWaitForDatabaseConnection
is placed outside the for loop - I oversaw this when simplifying it for stackoverflow. – KookaburraparallelLoopState.Stop
it will still be possible that new Tasks enter - but actually this can't happen - therefore I thought I need a Cancellation token... Do I always get an exception of typeAggregateException
if an exception is thrown in the loop body? – KookaburraAggregateException
. There is a possibility for other exceptions to be thrown (OperationCanceledException
,ArgumentNullException
andObjectDisposedException
), but these are unrelated to what happens inside the lambda. – PiezoelectricityparallelLoopState
as a parameter of theDoDatabaseCallAndInsertions
method, and inspect there frequently the propertyShouldExitCurrentIteration
, and exit fast in case it has becometrue
. This should increase the responsiveness of the parallel loop in case of an exception. – PiezoelectricityDoDatabaseCallAndInsertions
– KookaburraCancellationToken
to the data access layerExecute
method? – PiezoelectricityexecuteGeneric
on oracle database without abstraction (ODP oracle.com/webfolder/technetwork/tutorials/obe/db/dotnet/…) – Kookaburra