Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Uma tarefa filha (ou tarefa aninhada) é uma instância System.Threading.Tasks.Task criada no delegado do utilizador de outra tarefa, que é conhecida como a tarefa pai . Uma tarefa filho pode ser separada ou anexada. Uma tarefa filha separada é uma tarefa que é executada independentemente do seu progenitor. Um de tarefas filho anexado ao é uma tarefa aninhada que é criada com a opção TaskCreationOptions.AttachedToParent cujo pai não proíbe, explícita ou por padrão, que ela seja anexada. Uma tarefa pode criar qualquer quantia de tarefas subordinadas anexadas ou 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 subordinadas. | Não | Sim |
| O estatuto de progenitor depende do estatuto do filho. | Não | Sim |
Na maioria dos cenários, recomendamos que se use subtarefas independentes, porque as 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 opção TaskCreationOptions.AttachedToParent 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-mãe 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 Example4
{
public static void Main()
{
Task parent = Task.Factory.StartNew(() =>
{
Console.WriteLine("Outer task executing.");
Task 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 filial for representada por um objeto Task<TResult> em vez de um objeto Task, pode garantir que a tarefa pai aguardará a criança completar ao aceder à propriedade Task<TResult>.Result da criança, mesmo que seja uma tarefa filial desanexada. A propriedade Result bloqueia até que sua tarefa seja concluída, como mostra o exemplo a seguir.
using System;
using System.Threading;
using System.Threading.Tasks;
class Example3
{
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 {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 opção TaskCreationOptions.AttachedToParent na instrução de criação de tarefa, conforme mostrado no exemplo a seguir. Neste código, a tarefa filho anexada é concluída antes do 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 grafos fortemente sincronizados de operações assíncronas.
No entanto, uma tarefa subordinada só pode ser associada ao seu pai se o pai não proibir tarefas subordinadas associadas. As tarefas-mãe podem impedir explicitamente que tarefas filhas sejam ligadas a elas, especificando a opção TaskCreationOptions.DenyChildAttach no construtor de classe da tarefa-mãe ou no método TaskFactory.StartNew. As tarefas pai impedem implicitamente que as tarefas filho sejam anexadas a elas se forem criadas ao chamar o método Task.Run. O exemplo a seguir ilustra isso. É idêntico ao exemplo anterior, exceto que a tarefa pai é criada chamando o método Task.Run(Action) em vez do método TaskFactory.StartNew(Action). Como a tarefa filho não é capaz de se anexar ao pai, a saída do exemplo é imprevisível. Como as opções de criação de tarefas padrão para as sobrecargas de Task.Run incluem TaskCreationOptions.DenyChildAttach, este exemplo é funcionalmente equivalente ao primeiro exemplo na seção "Tarefas secundárias".
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example2
{
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 Task<TResult>.Result da tarefa. Portanto, ao utilizar tarefas filhas anexadas, é possível lidar com todas as exceções num único ponto na invocação de Task.Wait no thread de chamada. Para obter mais informações, consulte Exception Handling.
Cancelamento e tarefas infantis
O cancelamento de tarefas é cooperativo. Ou seja, para ser cancelável, cada subtarefa ligada ou desligada deve monitorizar o estado 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 a lógica para responder à solicitação dentro de cada tarefa. Para obter mais informações, consulte Cancelamento de Tarefas e Como cancelar uma tarefa e os seus subtarefas.
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 de filho desanexada é cancelada
Se uma tarefa-filho desanexada cancelar-se 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 é tratada como um cancelamento de cooperação benigno. Esse comportamento é o mesmo de qualquer tarefa de nível superior.
Quando uma tarefa secundária anexada é cancelada
Quando uma tarefa filial anexada se cancela utilizando o mesmo token que foi passado para a sua tarefa pai, um TaskCanceledException é propagado para o thread de união dentro de um AggregateException. Você deve aguardar a tarefa principal para poder lidar com todas as exceções benignas, além de todas as exceções causadoras de falhas que são propagadas através de uma rede de tarefas filhas associadas.
Para obter mais informações, consulte Exception Handling.
Impedindo que uma tarefa filho seja anexada ao pai
Uma exceção não tratada que é lançada por uma tarefa filha é propagada para a tarefa mãe. Podes usar este comportamento para observares todas as exceções das tarefas filhas de uma tarefa raiz do que percorrer uma árvore de tarefas. No entanto, a propagação de exceções pode ser problemática quando uma tarefa pai não espera a associação de algo de outro código. Por exemplo, considere uma aplicação que chama um componente de biblioteca de terceiros a partir de um objeto Task. Se o componente de biblioteca de terceiros também criar um objeto Task e especificar TaskCreationOptions.AttachedToParent para anexá-lo à tarefa principal, quaisquer exceções não-tratadas que ocorrerem na tarefa filha se propagarão para a tarefa principal. Isso pode levar a um comportamento inesperado no aplicativo principal.
Para impedir que uma tarefa filho seja anexada a uma tarefa pai, especifique a opção TaskCreationOptions.DenyChildAttach ao criar o objeto pai Task ou Task<TResult>. Quando uma tarefa tenta anexar-se ao seu pai e o pai especifica a opção TaskCreationOptions.DenyChildAttach, a tarefa filho não poderá anexar-se ao seu pai e será executada como se a opção TaskCreationOptions.AttachedToParent 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 principal não é concluída até que todas as tarefas subordinadas sejam concluídas, uma tarefa subordinada de longa execução pode levar a que o aplicativo no geral tenha um desempenho abaixo do esperado. Para um exemplo que mostra como melhorar o desempenho do aplicativo impedindo que uma tarefa filho se anexe à tarefa pai, consulte Como: Impedir que uma Tarefa Filho se Anexe à sua Tarefa Pai.