Tratamentul erorilor și anularea operațiunii
În timpul execuției operațiunilor paralele pot apărea erori, a căror tratare are particularitățile sale. În procesarea paralelă, colecția este împărțită în părți, iar fiecare parte este procesată într-un fir de execuție separat. Totuși, dacă apare o eroare într-unul dintre firele de execuție, sistemul întrerupe execuția tuturor firelor.
La generarea excepțiilor, toate acestea sunt agregate într-o excepție de tip AggregateException.
De exemplu, să presupunem că în metoda factorialului se transmite un array de obiecte care conține nu doar numere, ci și șiruri de caractere:
object[] numbers = new object[] { 1, 2, 3, 4, 5, "6" };
var squares = from n in numbers.AsParallel()
let x = (int)n
select Square(x);
try
{
squares.ForAll(n => Console.WriteLine(n));
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
int Square(int n) => n * n;
Lansăm proiectul fără depanare. Și deoarece array-ul conține un șir de caractere, încercarea de conversie va eșua, iar pe consolă va fi afișat un mesaj de eroare. La rularea aplicației în Visual Studio în modul de depanare, execuția se va opri la linia de conversie. După continuare, va funcționa și capturarea excepției în blocul catch, iar pe consolă va fi afișat mesajul de eroare.
Întreruperea operațiunii paralele
Este foarte probabil să avem nevoie să întrerupem o operațiune înainte de finalizarea acesteia. În acest caz, putem folosi metoda WithCancellation(), căreia i se transmite un token CancellationToken ca parametru:
CancellationTokenSource cts = new CancellationTokenSource();
// lansăm o sarcină suplimentară care întrerupe operațiunea după 400 ms
new Task(() =>
{
Thread.Sleep(400);
cts.Cancel();
}).Start();
try
{
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, };
var squares = from n in numbers.AsParallel().WithCancellation(cts.Token)
select Square(n);
foreach (var n in squares)
Console.WriteLine(n);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operațiunea a fost întreruptă");
}
catch (AggregateException ex)
{
if (ex.InnerExceptions != null)
{
foreach (Exception e in ex.InnerExceptions)
Console.WriteLine(e.Message);
}
}
finally
{
cts.Dispose();
}
int Square(int n)
{
var result = n * n;
Console.WriteLine($"Pătratul numărului {n} este {result}");
Thread.Sleep(1000); // imitația unui calcul de durată
return result;
}
În sarcina paralelă lansată, se apelează metoda cts.Cancel(), ceea ce duce la terminarea operațiunii și generarea excepției OperationCanceledException:
Pătratul numărului 5 este 25
Pătratul numărului 3 este 9
Pătratul numărului 1 este 1
Pătratul numărului 7 este 49
Operațiunea a fost întreruptă
În acest caz, este de asemenea logic să se trateze excepția AggregateException, deoarece dacă apare o altă excepție în paralel, aceasta și OperationCanceledException sunt plasate într-un singur obiect AggregateException.