Porady: obsługa wyjątków w zapytaniu PLINQ

W pierwszym przykładzie w tym temacie pokazano, jak obsłużyć System.AggregateException tę operację, która może zostać wyrzucona z zapytania PLINQ podczas wykonywania. W drugim przykładzie pokazano, jak umieścić bloki try-catch w delegatach, jak najbliżej miejsca, w którym zostanie zgłoszony wyjątek. W ten sposób można je przechwycić tak szybko, jak tylko wystąpią, i ewentualnie kontynuować wykonywanie zapytań. Gdy wyjątki mogą być bąbelkowe z powrotem do wątku przyłączania, możliwe jest, że zapytanie może nadal przetwarzać niektóre elementy po wystąpieniu wyjątku.

W niektórych przypadkach, gdy PLINQ wraca do wykonywania sekwencyjnego, a wystąpi wyjątek, wyjątek może być propagowany bezpośrednio i nie owinięty w AggregateExceptionobiekcie . ThreadAbortExceptionPonadto s są zawsze propagowane bezpośrednio.

Uwaga

Po włączeniu opcji "Just My Code" program Visual Studio przerwie działanie w wierszu, który zgłasza wyjątek i wyświetla komunikat o błędzie z komunikatem "Wyjątek nie jest obsługiwany przez kod użytkownika". Ten błąd jest łagodny. Możesz nacisnąć klawisz F5, aby kontynuować działanie, i zobaczyć zachowanie obsługi wyjątków, które przedstawiono w poniższych przykładach. Aby zapobiec uszkodzeniu pierwszego błędu programu Visual Studio, usuń zaznaczenie pola wyboru "Tylko mój kod" w obszarze Narzędzia, Opcje, Debugowanie, Ogólne.

Ten przykład ma na celu zademonstrowanie użycia i może nie działać szybciej niż równoważne sekwencyjne zapytanie LINQ to Objects. Aby uzyskać więcej informacji na temat przyspieszania, zobacz Understanding Speedup in PLINQ (Opis szybkości w PLINQ).

Przykład 1

W tym przykładzie pokazano, jak umieścić bloki try-catch wokół kodu, który wykonuje zapytanie w celu przechwycenia wszystkich System.AggregateExceptionzgłoszonych elementów.

// 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

W tym przykładzie zapytanie nie może kontynuować po wystąpieniu wyjątku. Po przechwyceniu wyjątku kod aplikacji PLINQ zatrzymał już zapytanie we wszystkich wątkach.

Przykład 2

W poniższym przykładzie pokazano, jak umieścić blok try-catch w delegacie, aby umożliwić przechwycenie wyjątku i kontynuowanie wykonywania zapytania.

// 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

Kompilowanie kodu

  • Aby skompilować i uruchomić te przykłady, skopiuj je do przykładu przykładu danych PLINQ i wywołaj metodę z pliku Main.

Niezawodne programowanie

Nie przechwyć wyjątku, chyba że wiesz, jak go obsłużyć, aby nie uszkodzić stanu programu.

Zobacz też