Como: Manipular exceções em uma consulta PLINQ
O primeiro exemplo neste tópico mostra como lidar com o System.AggregateException que pode ser lançado de uma consulta PLINQ quando ele é executado. O segundo exemplo mostra como colocar blocos try-catch dentro de delegados, o mais próximo possível de onde a exceção será lançada. Dessa forma, você pode capturá-los assim que ocorrerem e, possivelmente, continuar a execução da consulta. Quando as exceções são permitidas para borbulhar de volta para o thread de junção, então é possível que uma consulta pode continuar a processar alguns itens depois que a exceção é gerada.
Em alguns casos, quando o PLINQ retorna à execução sequencial e ocorre uma exceção, a exceção pode ser propagada diretamente e não encapsulada em um AggregateExceptionarquivo . Além disso, ThreadAbortExceptions são sempre propagados diretamente.
Nota
Quando "Just My Code" está habilitado, o Visual Studio quebrará na linha que lança a exceção e exibirá uma mensagem de erro que diz "exceção não manipulada pelo código do usuário". Este erro é benigno. Você pode pressionar F5 para continuar a partir dele e ver o comportamento de manipulação de exceções demonstrado nos exemplos abaixo. Para evitar que o Visual Studio quebre no primeiro erro, basta desmarcar a caixa de seleção "Apenas meu código" em Ferramentas, Opções, Depuração, Geral.
Este exemplo destina-se a demonstrar o uso e pode não ser executado mais rápido do que a consulta LINQ to Objects sequencial equivalente. Para obter mais informações sobre speedup, consulte Understanding Speedup in PLINQ.
Exemplo 1
Este exemplo mostra como colocar os blocos try-catch ao redor do código que executa a consulta para capturar qualquer System.AggregateExceptions que é lançado.
// Paste into PLINQDataSample class.
static void PLINQExceptions_1()
{
// Using the raw string array here. See PLINQ Data Sample.
string[] customers = GetCustomersAsStrings().ToArray();
// First, we must simulate some corrupt input.
customers[54] = "###";
var parallelQuery = from cust in customers.AsParallel()
let fields = cust.Split(',')
where fields[3].StartsWith("C") //throw indexoutofrange
select new { city = fields[3], thread = Thread.CurrentThread.ManagedThreadId };
try
{
// We use ForAll although it doesn't really improve performance
// since all output is serialized through the Console.
parallelQuery.ForAll(e => Console.WriteLine("City: {0}, Thread:{1}", e.city, e.thread));
}
// In this design, we stop query processing when the exception occurs.
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
Console.WriteLine(ex.Message);
if (ex is IndexOutOfRangeException)
Console.WriteLine("The data source is corrupt. Query stopped.");
}
}
}
' Paste into PLINQDataSample class
Shared Sub PLINQExceptions_1()
' Using the raw string array here. See PLINQ Data Sample.
Dim customers As String() = GetCustomersAsStrings().ToArray()
' First, we must simulate some corrupt input.
customers(20) = "###"
'throws indexoutofrange
Dim query = From cust In customers.AsParallel()
Let fields = cust.Split(","c)
Where fields(3).StartsWith("C")
Select fields
Try
' We use ForAll although it doesn't really improve performance
' since all output is serialized through the Console.
query.ForAll(Sub(e)
Console.WriteLine("City: {0}, Thread:{1}")
End Sub)
Catch e As AggregateException
' In this design, we stop query processing when the exception occurs.
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
If TypeOf ex Is IndexOutOfRangeException Then
Console.WriteLine("The data source is corrupt. Query stopped.")
End If
Next
End Try
End Sub
Neste exemplo, a consulta não pode continuar depois que a exceção é lançada. No momento em que o código do aplicativo captura a exceção, o PLINQ já interrompeu a consulta em todos os threads.
Exemplo 2
O exemplo a seguir mostra como colocar um bloco try-catch em um delegado para tornar possível capturar uma exceção e continuar com a execução da consulta.
// Paste into PLINQDataSample class.
static void PLINQExceptions_2()
{
var customers = GetCustomersAsStrings().ToArray();
// Using the raw string array here.
// First, we must simulate some corrupt input
customers[54] = "###";
// Assume that in this app, we expect malformed data
// occasionally and by design we just report it and continue.
static bool IsTrue(string[] f, string c)
{
try
{
string s = f[3];
return s.StartsWith(c);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine($"Malformed cust: {f}");
return false;
}
};
// Using the raw string array here
var parallelQuery =
from cust in customers.AsParallel()
let fields = cust.Split(',')
where IsTrue(fields, "C") //use a named delegate with a try-catch
select new { City = fields[3] };
try
{
// We use ForAll although it doesn't really improve performance
// since all output must be serialized through the Console.
parallelQuery.ForAll(e => Console.WriteLine(e.City));
}
// IndexOutOfRangeException will not bubble up
// because we handle it where it is thrown.
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}
' Paste into PLINQDataSample class
Shared Sub PLINQExceptions_2()
Dim customers() = GetCustomersAsStrings().ToArray()
' Using the raw string array here.
' First, we must simulate some corrupt input
customers(20) = "###"
' Create a delegate with a lambda expression.
' Assume that in this app, we expect malformed data
' occasionally and by design we just report it and continue.
Dim isTrue As Func(Of String(), String, Boolean) = Function(f, c)
Try
Dim s As String = f(3)
Return s.StartsWith(c)
Catch e As IndexOutOfRangeException
Console.WriteLine("Malformed cust: {0}", f)
Return False
End Try
End Function
' Using the raw string array here
Dim query = From cust In customers.AsParallel()
Let fields = cust.Split(","c)
Where isTrue(fields, "C")
Select New With {.City = fields(3)}
Try
' We use ForAll although it doesn't really improve performance
' since all output must be serialized through the Console.
query.ForAll(Sub(e) Console.WriteLine(e.City))
' IndexOutOfRangeException will not bubble up
' because we handle it where it is thrown.
Catch e As AggregateException
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
End Sub
Compilando o código
- Para compilar e executar esses exemplos, copie-os para o exemplo PLINQ Data Sample e chame o método de Main.
Programação robusta
Não pegue uma exceção, a menos que você saiba como lidar com isso para que você não corrompa o estado do seu programa.