Attività annidate e attività figlio
Un'attività annidata è semplicemente un'istanza di Task creata nel delegato dell'utente di un'altra attività. Un'attività figlio è un'attività annidata creata con l'opzione AttachedToParent. Un'attività può creare qualsiasi numero di attività figlio e/o annidate. L'unico limite è rappresentato dalle risorse di sistema disponibili. Nell'esempio seguente viene mostrata un'attività padre che crea un'unica attività annidata semplice.
Shared Sub SimpleNestedTask()
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
' Sample output:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.
static void SimpleNestedTask()
{
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.");
}
/* Sample output:
Outer task executing.
Nested task starting.
Outer has completed.
Nested task completing.
*/
Confronto fra attività figlio connesse e attività annidate disconnesse
Il punto più importante nel confronto fra attività figlio e attività annidate è che le attività annidate sono essenzialmente indipendenti dall'attività padre o esterna, mentre le attività figlio connesse sono strettamente sincronizzate con l'attività padre. Se si cambia l'istruzione di creazione dell'attività affinché utilizzi l'opzione AttachedToParent, come mostrato nell'esempio seguente,
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
var child = Task.Factory.StartNew((t) =>
{
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
viene prodotto l'output seguente.
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Parent task executing.
Attached child starting.
Attached child completing.
Parent has completed.
Le attività figlio connesse possono essere utilizzate per creare grafici strettamente sincronizzati di operazioni asincrone. Tuttavia, nella maggior parte degli scenari, è consigliabile utilizzare attività annidate poiché le relazioni con le altre attività sono meno complesse. È per questo motivo che per impostazione predefinita le attività create all'interno di altre attività sono annidate e che per creare un'attività figlio occorre specificare in modo esplicito l'opzione AttachedToParent.
Nella tabella seguente vengono elencate le differenze di base tra i due tipi di attività figlio.
Categoria |
Attività annidate |
Attività figlio connesse |
---|---|---|
L'attività esterna (padre) attende il completamento delle attività interne. |
No |
Sì |
L'attività padre propaga le eccezioni generate dalle attività figlio (interne). |
No |
Sì |
Lo stato dell'attività padre (esterna) dipende dallo stato dell'attività figlio (interna). |
No |
Sì |
Negli scenari di attività disconnesse in cui l'attività annidata è un oggetto Task<TResult> è comunque possibile forzare l'attività padre ad attendere un figlio accedendo alla proprietà Result dell'attività annidata. La proprietà Result viene bloccata fino al completamento della relativa attività.
Shared Sub WaitForSimpleNestedTask()
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
'Sample output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
static void WaitForSimpleNestedTask()
{
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);
}
/* Sample output:
Outer task executing.
Nested task starting.
Nested task completing.
Outer has returned 42.
*/
Eccezioni nelle attività annidate e figlio
Analogamente a quanto accade per qualsiasi attività non annidata, se un'attività annidata genera un'eccezione, quest'ultima deve essere osservata o gestita direttamente nell'attività esterna. Se un'attività figlio connessa genera un'eccezione, questa viene propagata automaticamente all'attività padre e ripropagata al thread che è in attesa o che tenta di accedere alla proprietà Result dell'attività. Pertanto, tramite l'utilizzo di attività figlio connesse è possibile gestire tutte le eccezioni in un solo punto, vale a dire nella chiamata a Wait nel thread chiamante. Per ulteriori informazioni, vedere Gestione delle eccezioni (Task Parallel Library).
Annullamento e attività figlio
Tenere presente che l'annullamento delle attività è cooperativo. Pertanto, affinché sia "annullabile", ogni attività figlio connessa o disconnessa deve monitorare lo stato del token di annullamento. Se si desidera annullare un'attività padre e tutti i relativi figli tramite un'unica richiesta di annullamento, passare lo stesso token come argomento a tutte le attività e fornire in ogni attività la logica per rispondere alla richiesta. Per ulteriori 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 di un'attività figlio (annidata), quest'ultima evidentemente non verrà mai avviata. Se un'attività padre annulla se stessa dopo l'avvio di un'attività figlio o annidata, l'attività annidata (figlio) verrà eseguita fino al completamento a meno che non presenti una propria logica di annullamento. Per ulteriori informazioni, vedere Annullamento delle attività.
Annullamento di un'attività annidata
Se un'attività figlio disconnessa annulla se stessa utilizzando lo stesso token passato all'attività e se l'attività padre non è in attesa del figlio, il sistema non propaga alcuna eccezione poiché l'eccezione viene trattata come un annullamento cooperativo benigno. Questo comportamento è uguale a quello di qualsiasi attività di primo livello.
Annullamento di un'attività figlio
Quando un'attività figlio connessa annulla se stessa utilizzando lo stesso token passato all'attività, il sistema propaga un oggetto TaskCanceledException nel thread di unione in un oggetto AggregateException. È molto importante restare in attesa dell'attività padre affinché sia possibile gestire tutte le eccezioni benigne oltre a tutte le eccezioni di errore propagate verso l'alto attraverso un grafico di attività figlio connesse.
Per ulteriori informazioni, vedere Gestione delle eccezioni (Task Parallel Library).