I am puzzled at the moment and can't think of any reasonable explanation. Maybe someone here has an explanation.
Setup:
I have a class that generates a lot of workitems to be computed with an ActionBlock
. The exact implementation is not relevant at this point but with a little change the problem will occur at an other dataset.
This was the original version
public static class Setups
{
private struct RunData
{
internal MyClass1 Setup;
internal MyClass2 Positions;
internal string Set;
}
private static readonly ActionBlock<RunData> ParallelWorker =
new(d => ProcessSetupsAsync(d.Setup, d.Positions, d.Set),
new ExecutionDataflowBlockOptions
{
BoundedCapacity = Environment.ProcessorCount * 10,
MaxDegreeOfParallelism = Environment.ProcessorCount,
SingleProducerConstrained = false
});
public static async Task GetSetups(ItemType[] bar1Filter,
ItemType[] bar2Filter, bool sameSet)
{
for (/* do some work*/)
{
foreach (RunData rd in MyMethod1(/*variables*/).Select(final
=> new RunData { Positions = positions, Set = set, Setup = final }))
{
if (!await ParallelWorker.SendAsync(rd).ConfigureAwait(false))
{
// Fails after running for about 15 minutes
// with ParallelWorker.Complete is true
throw new Exception("xxxxxxxx");
}
}
}
ParallelWorker.Complete();
await ParallelWorker.Completion.ConfigureAwait(false);
}
private static Task ProcessSetupsAsync
(MyClass1 setup, MyClass2 positions, string set)
{
try
{
List<MyClass1> setups = new();
/* Do some work */
return setups.Count > 0
? LocalAccess.AddSetupAsync(setups)
: Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine(ex); //<-Never gets hit.
throw;
}
My first guess was an exception inside the called method but this is clearly not the case.
I did figure it out. The parallel tasks did create an deadlock situation in the database. I noticed when looking at the database locks.
In VS Debugger I do see the SQLExceptions after changing the SendAsync call to
if (!await ParallelWorker.SendAsync(rd).ConfigureAwait(false))
{
await Task.Delay(TimeSpan.FromSeconds(60D)).ConfigureAwait(false);
throw new Exception("Should never happen");
}
and setting a break point on the throw new Exception line. Why the SqlException was not catched by the try/catch I do not know. So now I am confused and my believe in the try/catch mechanism is crumbling, but I am now on the right track.
I could confirm if I serialize the db calls the error goes away. I am now analyzing the database since I was under the impression that in an ROW_LOCK Scenario my data write should not overlap on the other hand there is an StoredProcedure that uses MERGE and this is known to be problematic...
The original question is therefore solved, but the question why the try/catch did not catch a simple SqlException did enter the field.