Attività figlio connesse e disconnesse
Un'attività figlio (o attività annidata) è un'istanza System.Threading.Tasks.Task creata nel delegato dell'utente di un'altra attività, noto come attività padre. Un'attività figlio può essere scollegata o collegata. Un'attività figlio scollegata è un'attività eseguita indipendentemente dall'attività padre. Un'attività figlio collegata è un'attività annidata creata con l'opzione TaskCreationOptions.AttachedToParent alla quale l'attività padre non impedisce il collegamento in modo esplicito o per impostazione predefinita. Un'attività può creare un numero qualsiasi di attività figlio collegate e scollegate, limitato solo dalle risorse di sistema.
La tabella seguente elenca le differenze di base tra i due tipi di attività figlio.
Categoria | Attività figlio scollegate | Attività figlio collegate |
---|---|---|
L'attività padre attende il completamento delle attività figlio. | No | Sì |
L'attività padre propaga le eccezioni generate dalle attività figlio. | No | Sì |
Lo stato dell'attività padre dipende dallo stato dell'attività figlio. | No | Sì |
Nella maggior parte degli scenari, è consigliabile usare l'attività figlio scollegata perché le relazioni con altre attività risultano meno complesse. Per questo motivo le attività create all'interno delle attività padre sono di tipo scollegato per impostazione predefinita ed è necessario specificare esplicitamente l'opzione TaskCreationOptions.AttachedToParent per creare un'attività figlio collegata.
Attività figlio scollegate
Anche se un'attività figlio viene creata da un'attività padre, per impostazione predefinita è indipendente dell'attività padre. Nell'esempio seguente, un'attività padre crea un'attività figlio semplice. Se si esegue più volte il codice di esempio, è possibile notare che l'output dell'esempio è diverso da quello indicato e potrebbe cambiare ogni volta che si esegue il codice. Il motivo è che l'attività padre e le attività figlio sono eseguite in modo indipendente; l'attività figlio è di tipo scollegato. Nell'esempio si attende solo il completamento dell'attività padre, mentre l'attività figlio potrebbe non essere eseguita o completata prima del termine dell'applicazione console.
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 l'attività figlio è rappresentata da un oggetto Task<TResult> anziché da un oggetto Task, è possibile assicurarsi che l'attività padre attenda il completamento dell'attività figlio accedendo alla proprietà Task<TResult>.Result dell'attività figlio anche è di tipo scollegato. La proprietà Result viene bloccata fino al completamento dell'attività, come mostrato nell'esempio seguente.
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
Attività figlio collegate
A differenza delle attività figlio scollegate, le attività figlio collegate sono strettamente sincronizzate con l'attività padre. È possibile modificare il tipo dell'attività figlio da scollegato a collegato usando l'opzione TaskCreationOptions.AttachedToParent nell'istruzione di creazione dell'attività, come mostrato nell'esempio seguente. In questo codice, l'attività figlio collegata viene completata prima dell'attività padre. Di conseguenza, l'output dell'esempio è lo stesso per ogni esecuzione del codice.
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.
Per creare grafici strettamente sincronizzati di operazioni asincrone, è possibile usare le attività figlio collegate.
Tuttavia, un'attività figlio può collegarsi all'attività padre solo se quest'ultimo consente le attività figlio collegate. Le attività padre possono impedire in modo esplicito il collegamento di attività figlio specificando l'opzione TaskCreationOptions.DenyChildAttach nel costruttore della classe dell'attività padre o il metodo TaskFactory.StartNew. Le attività padre possono impedire in modo implicito il collegamento di attività figlio se sono state create chiamando il metodo Task.Run. Ciò è illustrato nell'esempio seguente. L'esempio è uguale a quello precedente, ma in questo caso l'attività padre viene creata chiamando il metodo Task.Run(Action) anziché il metodo TaskFactory.StartNew(Action). Poiché l'attività figlio non è in grado di collegarsi all'attività padre, l'output dell'esempio è imprevedibile. Poiché le opzioni di creazione dell'attività predefinite per gli overload Task.Run includono TaskCreationOptions.DenyChildAttach, questo esempio è funzionalmente equivalente al primo esempio nella sezione "Attività figlio scollegate".
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.
Eccezioni nelle attività figlio
Se un'attività figlio scollegata genera un'eccezione, l'eccezione deve essere osservata o gestita direttamente nell'attività padre come accade nel caso di qualsiasi attività non annidata. Se un'attività figlio collegata genera un'eccezione, l'eccezione viene propagata automaticamente all'attività padre e al thread che rimane in attesa o tenta di accedere alla proprietà Task<TResult>.Result dell'attività. Quindi, usando le attività figlio collegate è possibile gestire tutte le eccezioni in un solo punto della chiamata a Task.Wait nel thread chiamante. Per altre informazioni, vedere Gestione delle eccezioni.
Annullamento e attività figlio
L'annullamento delle attività è cooperativo, ossia, per essere annullabile, ogni attività figlio collegata o scollegata deve monitorare lo stato del token di annullamento. Per annullare un'attività padre e le relative attività figlio usando un'unica richiesta, è necessario passare lo stesso token come argomento a tutte le attività e fornire la logica di risposta alla richiesta in ogni attività. Per altre informazioni, vedere Annullamento delle attività e Procedura: Annullare un'attività e i relativi figli.
Annullamento dell'attività padre
Se un'attività padre annulla se stessa prima dell'avvio dell'attività figlio, quest'ultima non viene avviata. Se un'attività padre annulla se stessa dopo l'avvio dell'attività figlio, quest'ultima viene completata a meno che non abbia una propria logica di annullamento. Per altre informazioni, vedere Task Cancellation.
Annullamento di un'attività figlio scollegata
Se un'attività figlio scollegata annulla se stessa usando lo stesso token passato all'attività padre e quest'ultima non attende l'attività figlio, non viene propagata alcuna eccezione poiché l'eccezione viene considerata come un annullamento cooperativo sicuro. Questo comportamento è uguale a quello di qualsiasi attività di primo livello.
Annullamento di un'attività figlio collegata
Quando un'attività figlio collegata annulla se stessa usando lo stesso token passato all'attività padre, TaskCanceledException viene propagato al thread di unione all'interno di AggregateException. È necessario attendere l'attività padre in modo che sia possibile gestire tutte le eccezioni sicure oltre a tutte le eccezioni di errore propagate tramite un grafico di attività figlio collegate.
Per altre informazioni, vedere Gestione delle eccezioni.
Impedire il collegamento di un'attività figlio all'attività padre
Un'eccezione non gestita generata da un'attività figlio viene propagata all'attività padre. È possibile usare questo comportamento per osservare tutte le eccezioni dell'attività figlio da un'attività radice invece di passare per un albero delle attività. Tuttavia, la propagazione delle eccezioni può essere problematica quando un'attività padre non prevede un allegato da altro codice. Si consideri ad esempio un'applicazione che chiama un componente della libreria di terze parti da un oggetto Task. Se il componente della libreria di terze parti crea anche un oggetto Task e specifica TaskCreationOptions.AttachedToParent per collegarlo all'attività padre, le eventuali eccezioni non gestite che si verificano nell'attività figlio vengono propagate all'attività padre. Questo potrebbe causare un comportamento imprevisto nell'applicazione principale.
Per impedire il collegamento di un'attività figlio all'attività padre, specificare l'opzione TaskCreationOptions.DenyChildAttach quando si crea l'attività padre Task o l'oggetto Task<TResult>. Quando un'attività tenta di connettersi all'attività padre che specifica l'opzione TaskCreationOptions.DenyChildAttach, l'attività figlio non riuscirà a collegarsi a un'attività padre e verrà eseguita come se l'opzione TaskCreationOptions.AttachedToParent non fosse stata specificata.
Se l'attività figlio non viene completata in modo tempestivo, è anche possibile impedirne il collegamento all'attività padre. Poiché un'attività padre non viene completata fino al completamento di tutte le attività figlio, un'attività figlio a esecuzione prolungata può influire negativamente sulle prestazioni dell'applicazione. Per un esempio in cui viene spiegato come migliorare le prestazioni dell'applicazione impedendo il collegamento di un'attività all'attività padre, vedere Procedura: Impedire il collegamento di un'attività figlio all'attività padre.