J'essaie de lancer et d'attraper une AggregateException. Je n'ai pas beaucoup utilisé d'exceptions en C #, mais le comportement que j'ai trouvé est un peu surprenant.
Mon code est:
var numbers = Enumerable.Range(0, 20);
try
{
var parallelResult = numbers.AsParallel()
.Where(i => IsEven(i));
parallelResult.ForAll(e => Console.WriteLine(e));
}
catch (AggregateException e)
{
Console.WriteLine("There was {0} exceptions", e.InnerExceptions.Count());
}
Il appelle la fonction IsEven
private static bool IsEven(int i)
{
if (i % 10 == 0)
throw new AggregateException("i");
return i % 2 == 0;
}
Cela lève l'exception AggregateException.
Je m'attendrais à ce que le code écrive chaque nombre pair dans la plage 0,20 et "Il y avait 1 exceptions" deux fois.
Ce que j'obtiens, c'est quelques chiffres imprimés (ils sont une cause aléatoire de ForAll), puis l'exception est levée, mais pas interceptée et les programmes s'arrêtent.
Suis-je en train de manquer quelque chose?
C'est en fait assez intéressant. Je pense que le problème est que vous utilisez AggregateException
d'une manière inattendue, ce qui provoque une erreur dans le code PLINQ.
L'intérêt de AggregateException
est de regrouper plusieurs exceptions qui peuvent se produire simultanément (ou presque) dans un processus parallèle. Ainsi, AggregateException
devrait avoir au moins une exception interne. Mais vous lancez new AggregateException("i")
, qui n'a pas d'exceptions internes. Le code PLINQ essaie d'examiner la propriété InnerExceptions
, frappe une sorte d'erreur (probablement une NullPointerException
), puis il semble entrer dans une boucle quelconque. Il s'agit sans doute d'un bogue dans PLINQ, car vous utilisez un constructeur valide pour AggregateException
, même s'il est inhabituel.
Comme indiqué ailleurs, lancer ArgumentException
serait plus sémantiquement correct. Mais vous pouvez obtenir le comportement que vous recherchez en lançant un AggregateException
correctement construit, par exemple en changeant la fonction IsEven
en quelque chose comme ceci:
private static bool IsEven(int i)
{
if (i % 10 == 0){
//This is still weird
//You shouldn't do this. Just throw the ArgumentException.
throw new AggregateException(new ArgumentException("I hate multiples of 10"));
}
return i % 2 == 0;
}
Je pense que la morale de l'histoire est de ne pas lancer AggregateException
à moins que vous ne sachiez vraiment exactement ce que vous faites, en particulier si vous êtes déjà dans une opération parallèle ou basée sur Task
.
Je suis d'accord avec les autres: c'est un bogue dans .Net et vous devriez le signaler .
La cause est dans la méthode QueryEnd()
dans la classe interne QueryTaskGroupState
. Son code décompilé (et légèrement modifié pour plus de clarté) ressemble à ceci:
try
{
this.m_rootTask.Wait();
}
catch (AggregateException ex)
{
AggregateException aggregateException = ex.Flatten();
bool cacellation = true;
for (int i = 0; i < aggregateException.InnerExceptions.Count; ++i)
{
var canceledException =
aggregateException.InnerExceptions[i] as OperationCanceledException;
if (IsCancellation(canceledException))
{
cacellation = false;
break;
}
}
if (!cacellation)
throw aggregateException;
}
finally
{
this.m_rootTask.Dispose();
}
if (!this.m_cancellationState.MergedCancellationToken.IsCancellationRequested)
return;
if (!this.m_cancellationState.TopLevelDisposedFlag.Value)
CancellationState.ThrowWithStandardMessageIfCanceled(
this.m_cancellationState.ExternalCancellationToken);
if (!userInitiatedDispose)
throw new ObjectDisposedException(
"enumerator", "The query enumerator has been disposed.");
Fondamentalement, cela signifie:
AggregateException
aplati s'il contient des exceptions de non-annulationObjectDisposedException
pour une raison quelconque (en supposant que userInitiatedDispose
est false
, ce qui est le cas)Donc, si vous lancez un AggregateException
sans exception interne, ex
sera un AggregateException
contenant votre AggregateExcaption
vide. L'appel de Flatten()
transformera cela en un simple AggreateException
, ce qui signifie qu'il ne contient aucune exception de non-annulation, donc la première partie du code pense que c'est une annulation et non jeter.
Mais la deuxième partie du code se rend compte que ce n'est pas une annulation, donc il lève une exception complètement fausse.