Sdílet prostřednictvím


Zpracovávání vyjímek (Task Parallel Library)

Neošetřené vyjímky, které jsou vyvolány uživatelským kódem bežícím uvnitř úlohy, jsou šířeny zpět do spojovacího vlákna, s vyjímkou speciálních scénářů, které budou popsány později v tomto tématu. Výjimky jsou šířeny při použití statickému nebo instance Task.Wait nebo Task<TResult>.Wait metod a zpracování jejich uzavřením volání v příkazu try-catch. Pokud je úloha nadřazená připojené podřízené úloze nebo pokud čekáte na více úkolů, může být vyvoláno více výjimek. K šíření všech výjimek zpět do hlavního vlákna, je nástroj ošetřování vyjímek zabalí do AggregateException instance. AggregateExceptionInnerExceptions vlastnosti, které mohou přezkoumat k zjištění původních výjimek, které byly vyvolána. Tyto vyjímky je pak možné zpracovat (nebo nezpracovat) samostatně. I v případě vyvoláni pouze jedné výjimka, je vyjímka stále obalena do AggregateException.

Dim task1 = Task.Factory.StartNew(Sub()
                                      Throw New MyCustomException("I'm bad, but not too bad!")
                                  End Sub)

Try
    task1.Wait()
Catch ae As AggregateException
    ' Assume we know what's going on with this particular exception.
    ' Rethrow anything else. AggregateException.Handle provides
    ' another way to express this. See later example.
    For Each ex In ae.InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            Console.WriteLine(ex.Message)
        Else
            Throw
        End If
    Next

End Try
var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("I'm bad, but not too bad!");
});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{
    // Assume we know what's going on with this particular exception.
    // Rethrow anything else. AggregateException.Handle provides
    // another way to express this. See later example.
    foreach (var e in ae.InnerExceptions)
    {
        if (e is MyCustomException)
        {
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }

}

Nezpracované výjimce se můžete vyhnout zachycením AggregateException a nepozorováním vnitřních výjimek. Doporučujeme však toto nastavení nepoužívat, protože je analogická zachytávaní zakládního typu vyjímek v ne-paralelní scénářích. Zachytit výjimku a poté neprovést žádnou specifickou akci zotavení, může program nechat v neurčitém stavu.

Pokud nečekáte na úlohu, který rozšíří výjimku nebo přistoupí k její Exception vlastnosti, výjimka se eskaluje podle zásad práce s výjimkami .NET pří uvolňování paměti úlohy.

Pokud je výjimkám umožněno probublat zpět do spojovacího vlákna, je možné, aby úloha pokračovala ve zpracování některých položek i po tom co nastane vyjímka.

PoznámkaPoznámka

Pokud je povolená vlastnost "Pouze můj kód" , Visual Studio v některých případech zastaví na řádce, která vyvolala vyjímku a zobrazí chybovou zprávu s upozorněním, že výjimka není zpracována uživatelským kódem. Tato chyba je neškodná.Můžete stisknout klávesu F5 k pokračování a zjistit jak jsou zpracovaný vyjímky. Toto je znázorněno v těchto příkladech.Chcete-li zabránit Visual Studiu v zastavení na první chybě, zrušte zaškrtávací políčko "Pouze můj kód" v menu Nástroje, Možnosti ladění, Obecné.

Připojené podřízený úlohy a vnořené AggregateExceptions

Pokud úloha obsahuje připojené podřízené úlohy, které způsobí výjimku, tato výjimka je zabalené do AggregateException předtím než je postoupená do nadřazené úlohy, který zabalí tuto výjimka do své vlastní AggregateException dříve, než jej postoupí zpět do volajícího vlákna. V takových případech AggregateException().InnerExceptions Vlastnost AggregateException , ulovených v Task.Wait nebo Task<TResult>.Wait nebo WaitAny nebo WaitAll Metoda obsahuje jeden nebo více AggregateException instance, nejsou původní výjimky, které způsobily chyby. Vyhnout iterovat vnořené přes AggregateExceptions, můžete použít Flatten() Metoda odebrat všechny vnořené AggregateExceptions tak, aby AggregateException() InnerExceptions vlastnost obsahuje původní výjimky. V následujícím příkladu vnořené AggregateException jsou instance sloučí a zpracováno v jeden cyklus.

' task1 will throw an AE inside an AE inside an AE
Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim child1 = Task.Factory.StartNew(Sub()
                                                                             Dim child2 = Task.Factory.StartNew(Sub()
                                                                                                                    Throw New MyCustomException("Attached child2 faulted.")
                                                                                                                End Sub,
                                                                                                                TaskCreationOptions.AttachedToParent)
                                                                         End Sub,
                                                                         TaskCreationOptions.AttachedToParent)
                                      ' Uncomment this line to see the exception rethrown.
                                      ' throw new MyCustomException("Attached child1 faulted.")
                                  End Sub)
Try
    task1.Wait()
Catch ae As AggregateException
    For Each ex In ae.Flatten().InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            Console.WriteLine(ex.Message)
        Else
            Throw
        End If
    Next
    'or like this:
    '  ae.Flatten().Handle(Function(e)
    '                               Return TypeOf (e) Is MyCustomException
    '                   End Function)
End Try
// task1 will throw an AE inside an AE inside an AE
var task1 = Task.Factory.StartNew(() =>
{
    var child1 = Task.Factory.StartNew(() =>
        {
            var child2 = Task.Factory.StartNew(() =>
            {
                throw new MyCustomException("Attached child2 faulted.");
            },
            TaskCreationOptions.AttachedToParent);

            // Uncomment this line to see the exception rethrown.
            // throw new MyCustomException("Attached child1 faulted.");
        },
        TaskCreationOptions.AttachedToParent);
});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{

    foreach (var e in ae.Flatten().InnerExceptions)
    {
        if (e is MyCustomException)
        {
            // Recover from the exception. Here we just
            // print the message for demonstration purposes.
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }
    // or ...
   // ae.Flatten().Handle((ex) => ex is MyCustomException);

}

Výjimky z odpojené podřízené úlohy.

Ve výchozím nastavení jsou podřízené úlohy vytvořen jako odpojené. Vyjímky z odpojené úlohy musí být zpracovány nebo znovu vyvolány ihned v nadřazené úloze. Tyto vyjímky nejsou postoupeny zpět do volajícího vlákna stejným způsobem jako připojené úlohy. Nejhornější nadřazená úloha může ručně znovu vyvolat výjimku z odpojené podřízené úlohy. Takto znovu vyvolaná vyjímka bude zabalena do AggregateException a postoupena zpět do spojovacího vlákno.

Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim nestedTask1 = Task.Factory.StartNew(Sub()
                                                                                  Throw New MyCustomException("Nested task faulted.")
                                                                              End Sub)
                                      ' Here the exception will be escalated back to joining thread.
                                      ' We could use try/catch here to prevent that.
                                      nestedTask1.Wait()
                                  End Sub)
Try
    task1.Wait()
Catch ae As AggregateException
    For Each ex In ae.Flatten().InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            ' Recover from the exception. Here we just
            ' print the message for demonstration purposes.
            Console.WriteLine(ex.Message)
        End If
    Next
End Try
var task1 = Task.Factory.StartNew(() =>
{

    var nested1 = Task.Factory.StartNew(() =>
    {
        throw new MyCustomException("Nested task faulted.");
    });

    // Here the exception will be escalated back to joining thread.
    // We could use try/catch here to prevent that.
    nested1.Wait();

});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{

    foreach (var e in ae.Flatten().InnerExceptions)
    {
        if (e is MyCustomException)
        {
            // Recover from the exception. Here we just
            // print the message for demonstration purposes.
            Console.WriteLine(e.Message);
        }
    }
}

I když používáte pokračování k sledování výjimek v podřízené úloze, výjimka musí být stále sledována nadřazenou úlohou.

Výjimky, které určují kooperativní zrušení

Pokud uživatelský kód v úloze odpoví na požadavek zrušení, mělo by správně dojít k vyvoláni OperationCanceledException a jejího předání v rušícího tokenu, na kterém byla žádost sdělena. Před pokusem o postoupení výjimky, úloha porovná token ve výjimce s tokenem, který jí byl předán při vytváření. Pokud jsou stejné, daná úloha postoupí TaskCanceledException zabalenou v AggregateException a lze ji zobrazit zkoumání vnořené výjimky. Pokud však spojovací vlákno nečeká na úlohu, tato specifická výjimka není postoupena. Další informace naleznete v tématu Zrušení úlohy.

Dim someCondition As Boolean = True
Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token

Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim ct As CancellationToken = token
                                      While someCondition = True
                                          ' Do some work...
                                          Thread.SpinWait(500000)
                                          ct.ThrowIfCancellationRequested()
                                      End While
                                  End Sub,
                                  token)
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var task1 = Task.Factory.StartNew(() =>
{
    CancellationToken ct = token;
    while (someCondition)
    {
        // Do some work...
        Thread.SpinWait(50000);
        ct.ThrowIfCancellationRequested();
    }
},
token);

// No waiting required.

Použití Metody zpracování k filtrování vnitřní výjimek.

Můžete použítHandle() metodu k odfiltrovat výjimek, které lze považovat jako "zpracování" bez použití jakékoli další logiky. V uživatelském delegátu dodaný do Handle(), můžete prověřit typ výjimky nebo jeho Message() vlastnost nebo jakékoli další informace, které vám umožní zjistit, zda je vyjímka neškodné. Jakékoli výjimky pro které delegát vrátí false jsou znovu vyvolány v nové AggregateException bezprostředně po ukončení Handle() metody.

Následující úryvek používá foreach smyčku přes vnitřní výjimky.

For Each ex In ae.InnerExceptions
    If TypeOf (ex) Is MyCustomException Then
        Console.WriteLine(ex.Message)
    Else
        Throw
    End If
Next
foreach (var e in ae.InnerExceptions)
{
    if (e is MyCustomException)
    {
        Console.WriteLine(e.Message);
    }
    else
    {
        throw;
    }
}

Následující úryvek ukazuje funkčně ekvivalentní použití Handle() metody.

ae.Handle(Function(ex)
              Return TypeOf (ex) Is MyCustomException
          End Function)
ae.Handle((ex) =>
{
    return ex is MyCustomException;
});

Pozorování vyjímek za použití Task.Exception vlastnosti.

Pokud úkol dokončí v Faulted státu, jeho Exception vlastnost můžete zkoumat zjistit, která zvláštní výjimka způsobila chybu. Vhodný způsob, jak sledovat Exception vlastnost, je použití pokračování, které se spouští pouze v případě závady předchozí úlohy. Tento způsob je znázorněno v následujícím příkladu.

Dim task1 = Task.Factory.StartNew(Sub()
                                      Throw New MyCustomException("task1 faulted.")
                                  End Sub).ContinueWith(Sub(t)
                                                            Console.WriteLine("I have observed a {0}", _
                                                                              t.Exception.InnerException.GetType().Name)
                                                        End Sub,
                                                        TaskContinuationOptions.OnlyOnFaulted)
var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
    {
        Console.WriteLine("I have observed a {0}",
            t.Exception.InnerException.GetType().Name);
    },
    TaskContinuationOptions.OnlyOnFaulted);

V reálné aplikaci může delegát pokračování protokolovat podrobné informace o výjimce a případně spustit nový úkol k zotavit z výjimky.

Událost UnobservedTaskException

V některých scénářích, jako je například hostování nedůvěryhodných zásuvných modulu, můžou být neškodné výjimky společné a může být příliš obtížné je všechny ručně sledovat. V těchto případech, je možné zpracovávat TaskScheduler.UnobservedTaskException událost. Instance System.Threading.Tasks.UnobservedTaskExceptionEventArgs je předána a je možné ji použít k zabránění propagování nesledovaných vyjímek zpět do spojovacího vlákna.

Viz také

Koncepty

Knihovna paralelních úloh