(Biblioteca paralela de tarefas) de manipulação de exceção

Exceções sem tratamento lançadas pelo código do usuário que está sendo executado de uma tarefa são propagadas para o segmento de ingresso, exceto em determinados cenários descritos neste tópico. Exceções são propagadas ao usar um dos estática ou instância Task.Wait ou Task<TResult>.Wait métodos e tratá-las colocando-se a chamada em uma instrução try-catch. Se uma tarefa é o pai de tarefas filho anexado, ou se você estiver esperando em diversas tarefas, várias exceções foi lançadas. Para propagar todas as exceções de volta para o segmento de chamada, a infra-estrutura de tarefa quebra em uma AggregateException instância. O AggregateException tem um InnerExceptions propriedade que pode ser enumerada para examinar todas as exceções originais que foram lançadas e manipular (ou não tratar) cada um deles individualmente. Mesmo que apenas uma exceção é lançada, ainda é empacotada em um 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;
        }
    }

}

Você pode evitar uma exceção não tratada ao detectar apenas o AggregateException e não observar qualquer uma das exceções interna. No entanto, recomendamos que você não faça isso porque é análogo ao detectar o tipo de exceção base em cenários de não paralelos. Para capturar uma exceção sem realizar ações específicas para recuperá-lo pode deixar o programa em um estado indeterminado.

Se você não esperar por uma tarefa que se propaga uma exceção ou acessar a propriedade de exceção, a exceção é escalada de acordo com à.Diretiva de exceção NET quando a tarefa é a coleta de lixo.

Quando são permitidas exceções a emergir volta para o segmento de ingresso, em seguida, é possível que uma tarefa pode continuar a processar alguns itens depois que a exceção é gerada.

Observação

Quando "apenas meu código" é ativado, Visual Studio em alguns casos será quebrar na linha que lança a exceção e exibirá uma mensagem de erro que diz "exceção não tratada pelo código do usuário". Este erro é benigno.Você pode pressionar F5 para continuar e ver o comportamento de manipulação de exceção que é demonstrado nesses exemplos.Para evitar que Visual Studio no primeiro erro, basta desmarcar o "Just My Code" caixa de seleção em Ferramentas, opções, depuração, geral.

Tarefas de filho anexado e aninhadas AggregateExceptions

Se uma tarefa tiver uma tarefa filho anexado que lança uma exceção, essa exceção é empacotada em um AggregateException antes de ele é propagado para a tarefa pai, que envolve essa exceção no seu próprio AggregateException antes de ele propaga volta para o segmento de chamada. Em tais casos, o AggregateException().InnerExceptions propriedade da AggregateException que é detectada na Task.Wait ou Task<TResult>.Wait ou WaitAny ou WaitAll método contém um ou mais AggregateException ocorrências, não as exceções originais que causou a falha. Para evitar ter de iterar sobre aninhado AggregateExceptions, você pode usar o Flatten() método para remover todos os AggregateExceptions aninhadas, para que o AggregateException() InnerExceptions propriedade contém exceções original. No exemplo a seguir, aninhados AggregateException instâncias são achatadas e manipuladas em apenas um loop.

' 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);

}

Exceções a partir de tarefas separado do filho

Por padrão, as tarefas filho são criadas como desanexado. As exceções geradas por desanexado tarefas devem ser manipuladas ou relançadas na tarefa pai imediato; eles não são propagados para o segmento de chamada as mesmas tarefas filho anexados maneira tão propagada de volta. Pai superior manualmente pode relançar a exceção de um filho separado para fazer com que ele seja disposto em um AggregateException e propagada de volta para o segmento de ingresso.

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);
        }
    }
}

Mesmo que você use uma continuação para observar uma exceção em uma tarefa filho, a exceção ainda deve ser observada pela tarefa pai.

Exceções que indicam o cancelamento cooperativo

Quando o código do usuário em uma tarefa responde a uma solicitação de cancelamento, o procedimento correto é lançar um OperationCanceledException transmitindo o token de cancelamento, no qual a solicitação foi comunicada. Antes de tentar propagar a exceção, a instância de tarefa compara o token de exceção para aquele que foi passado para ele, quando ele foi criado. Se eles forem iguais, a tarefa se propaga um TaskCanceledException disposto na AggregateException, e pode ser visto quando as exceções internas são examinadas. No entanto, se o thread de ingresso não está aguardando a tarefa, esta exceção específica não será propagada. Para obter mais informações, consulte Cancelamento da tarefa.

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.

Usando o método da alça para exceções de filtro interno

Você pode usar o Handle() método para filtrar exceções que você pode tratar como "tratados" sem usar qualquer lógica adicional. O delegado do usuário que é fornecida para Handle(), você pode examinar o tipo de exceção, ou seu Message() propriedade ou outras informações sobre ela que permitirá que você a determinar se é benigno. Quaisquer exceções para o qual o delegado retorna false são relançadas em uma nova AggregateException instância imediatamente após a Handle() retorna.

O trecho a seguir utiliza um foreach loop sobre exceções internas.

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;
    }
}

O trecho a seguir mostra o uso funcionalmente equivalente a Handle() método.

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

Observando as exceções usando a propriedade Task.Exception

Se uma tarefa for concluída na Faulted estado, sua Exception propriedade pode ser examinada para descobrir qual exceção específica causou a falha. Uma boa maneira de observar o Exception é de propriedade usar uma continuação, que é executado somente se a tarefa antecedente de falhas, como mostrado no exemplo a seguir.

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);

Em um aplicativo real, o delegado de continuação pode registrar informações detalhadas sobre a exceção e possivelmente gerar novas tarefas para recuperar a exceção.

Evento de UnobservedTaskException

Em alguns cenários, como, por exemplo, ao hospedar plug-ins não confiáveis, benignas exceções podem ser comuns, e pode ser muito difícil manualmente observe todos eles. Nesses casos, você pode manipular o TaskScheduler.UnobservedTaskException de evento. O System.Threading.Tasks.UnobservedTaskExceptionEventArgs que é passada para o manipulador de instância pode ser usada para evitar a exceção unobserved sendo propagadas de volta para o segmento de ingresso.

Consulte também

Conceitos

Biblioteca paralela de tarefas