Tarefas Anexadas e Separadas da Criança
Uma tarefa filho (ou tarefa aninhada) é uma System.Threading.Tasks.Task instância que é criada no delegado de usuário de outra tarefa, que é conhecida como a tarefa pai. Uma tarefa filho pode ser separada ou anexada. Uma tarefa filho desanexada é uma tarefa que é executada independentemente de seu pai. Uma tarefa filho anexada é uma tarefa aninhada que é criada com a TaskCreationOptions.AttachedToParent opção cujo pai não proíbe, explícita ou por padrão, que ela seja anexada. Uma tarefa pode criar qualquer número de tarefas filhas anexadas e desanexadas, limitadas apenas pelos recursos do sistema.
A tabela a seguir lista as diferenças básicas entre os dois tipos de tarefas filhas.
Categoria | Tarefas separadas da criança | Tarefas secundárias anexadas |
---|---|---|
O pai aguarda a conclusão das tarefas do filho. | Não | Sim |
O pai propaga exceções lançadas por tarefas filhas. | Não | Sim |
O estatuto de progenitor depende do estatuto do filho. | Não | Sim |
Na maioria dos cenários, recomendamos que você use tarefas filhas separadas, porque suas relações com outras tarefas são menos complexas. É por isso que as tarefas criadas dentro das tarefas pai são desanexadas por padrão, e você deve especificar explicitamente a TaskCreationOptions.AttachedToParent opção para criar uma tarefa filho anexada.
Tarefas separadas da criança
Embora uma tarefa filho seja criada por uma tarefa pai, por padrão ela é independente da tarefa pai. No exemplo a seguir, uma tarefa pai cria uma tarefa filho simples. Se você executar o código de exemplo várias vezes, você pode notar que a saída do exemplo difere do mostrado e também que a saída pode mudar cada vez que você executar o código. Isso ocorre porque a tarefa pai e as tarefas filho são executadas independentemente uma da outra; A criança é uma tarefa desprendida. O exemplo aguarda apenas a conclusão da tarefa pai, e a tarefa filho pode não ser executada ou concluída antes que o aplicativo de console seja encerrado.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});
parent.Wait();
Console.WriteLine("Outer has completed.");
}
}
// The example produces output like the following:
// Outer task executing.
// Nested task starting.
// Outer has completed.
// Nested task completing.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(500000)
Console.WriteLine("Nested task completing.")
End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
End Module
' The example produces output like the following:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.
Se a tarefa filho for representada por um Task<TResult> objeto em vez de um Task objeto, você poderá garantir que a tarefa pai aguardará a conclusão do filho acessando a Task<TResult>.Result propriedade do filho, mesmo que seja uma tarefa filho desanexada. A Result propriedade bloqueia até que sua tarefa seja concluída, como mostra o exemplo a seguir.
using System;
using System.Threading;
using System.Threading.Tasks;
class Example
{
static void Main()
{
var outer = Task<int>.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");
var nested = Task<int>.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Nested task completing.");
return 42;
});
// Parent will wait for this detached child.
return nested.Result;
});
Console.WriteLine("Outer has returned {0}.", outer.Result);
}
}
// The example displays the following output:
// Outer task executing.
// Nested task starting.
// Nested task completing.
// Outer has returned 42.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Nested task completing.")
Return 42
End Function)
Return child.Result
End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
End Module
' The example displays the following output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
Tarefas secundárias anexadas
Ao contrário das tarefas filhas separadas, as tarefas filhas anexadas são estreitamente sincronizadas com o pai. Você pode alterar a tarefa filho desanexada no exemplo anterior para uma tarefa filho anexada usando a TaskCreationOptions.AttachedToParent opção na instrução de criação de tarefa, conforme mostrado no exemplo a seguir. Neste código, a tarefa filho anexada é concluída antes de seu pai. Como resultado, a saída do exemplo é a mesma toda vez que você executa o código.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays the following output:
// Parent task executing.
// Attached child starting.
// Attached child completing.
// Parent has completed.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task executing")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays the following output:
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Você pode usar tarefas filho anexadas para criar gráficos fortemente sincronizados de operações assíncronas.
No entanto, uma tarefa subordinada só pode ser anexada ao pai se o pai não proibir tarefas secundárias anexadas. As tarefas pai podem impedir explicitamente que tarefas filhas sejam anexadas a elas, especificando a TaskCreationOptions.DenyChildAttach opção no construtor de classe da tarefa pai ou no TaskFactory.StartNew método. As tarefas pai implicitamente impedem que as tarefas filho sejam anexadas a elas se forem criadas chamando o Task.Run método. O exemplo a seguir ilustra isso. É idêntico ao exemplo anterior, exceto que a tarefa pai é criada chamando o Task.Run(Action) método em vez do TaskFactory.StartNew(Action) método. Como a tarefa filho não é capaz de anexar ao pai, a saída do exemplo é imprevisível. Como as opções de criação de tarefas padrão para as Task.Run sobrecargas incluem TaskCreationOptions.DenyChildAttach, este exemplo é funcionalmente equivalente ao primeiro exemplo na seção "Tarefas filhas desanexadas".
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Run(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays output like the following:
// Parent task executing.
// Parent has completed.
// Attached child starting.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Run(Sub()
Console.WriteLine("Parent task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task executing.
' Parent has completed.
' Attached child starting.
Exceções em tarefas secundárias
Se uma tarefa filho desanexada lançar uma exceção, essa exceção deverá ser observada ou tratada diretamente na tarefa pai, assim como em qualquer tarefa não aninhada. Se uma tarefa filho anexada lançar uma exceção, a exceção será propagada automaticamente para a tarefa pai e de volta para o thread que aguarda ou tenta acessar a propriedade da Task<TResult>.Result tarefa. Portanto, usando tarefas filho anexadas, você pode lidar com todas as exceções em apenas um ponto na chamada para Task.Wait no thread de chamada. Para obter mais informações, consulte Tratamento de exceções.
Cancelamento e tarefas infantis
O cancelamento de tarefas é cooperativo. Ou seja, para ser cancelável, cada tarefa filho anexada ou desanexada deve monitorar o status do token de cancelamento. Se quiser cancelar um pai e todos os seus filhos usando uma solicitação de cancelamento, passe o mesmo token como argumento para todas as tarefas e forneça em cada tarefa a lógica para responder à solicitação em cada tarefa. Para obter mais informações, consulte Cancelamento de tarefas e Como cancelar uma tarefa e seus filhos.
Quando o pai cancela
Se um pai se cancelar antes de sua tarefa filho ser iniciada, a criança nunca será iniciada. Se um pai se cancelar depois que sua tarefa filho já tiver começado, a criança será concluída, a menos que tenha sua própria lógica de cancelamento. Para obter mais informações, consulte Cancelamento de tarefas.
Quando uma tarefa filho desanexada é cancelada
Se uma tarefa filho desanexada se cancelar usando o mesmo token que foi passado para o pai, e o pai não esperar pela tarefa filho, nenhuma exceção será propagada, porque a exceção será tratada como cancelamento de cooperação benigna. Esse comportamento é o mesmo de qualquer tarefa de nível superior.
Quando uma tarefa secundária anexada é cancelada
Quando uma tarefa filho anexada se cancela usando o mesmo token que foi passado para sua tarefa pai, a TaskCanceledException é propagada para o thread de junção dentro de um AggregateExceptionarquivo . Você deve aguardar a tarefa pai para poder lidar com todas as exceções benignas, além de todas as exceções com falhas que são propagadas por meio de um gráfico de tarefas filhas anexadas.
Para obter mais informações, consulte Tratamento de exceções.
Impedindo que uma tarefa filho seja anexada ao pai
Uma exceção não tratada que é lançada por uma tarefa filho é propagada para a tarefa pai. Você pode usar esse comportamento para observar todas as exceções de tarefa filho de uma tarefa raiz em vez de percorrer uma árvore de tarefas. No entanto, a propagação de exceções pode ser problemática quando uma tarefa pai não espera anexo de outro código. Por exemplo, considere um aplicativo que chama um componente de biblioteca de terceiros de um Task objeto. Se o componente de biblioteca de terceiros também criar um Task objeto e especificar TaskCreationOptions.AttachedToParent anexá-lo à tarefa pai, quaisquer exceções não tratadas que ocorrerem na tarefa filho se propagarão para o pai. Isso pode levar a um comportamento inesperado no aplicativo principal.
Para impedir que uma tarefa filho seja anexada à tarefa pai, especifique a TaskCreationOptions.DenyChildAttach opção ao criar o pai Task ou Task<TResult> objeto. Quando uma tarefa tenta anexar ao pai e o pai especifica a TaskCreationOptions.DenyChildAttach opção, a tarefa filho não poderá ser anexada a um pai e será executada como se a TaskCreationOptions.AttachedToParent opção não fosse especificada.
Você também pode querer impedir que uma tarefa filho seja anexada ao pai quando a tarefa filho não for concluída em tempo hábil. Como uma tarefa pai não é concluída até que todas as tarefas filho sejam concluídas, uma tarefa filho de longa execução pode fazer com que o aplicativo geral tenha um desempenho insatisfatório. Para obter um exemplo que mostra como melhorar o desempenho do aplicativo impedindo que uma tarefa seja anexada à tarefa pai, consulte Como impedir que uma tarefa filho seja anexada ao pai.