Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Un'attività figlio (o attività nidificata) è un'istanza System.Threading.Tasks.Task che viene creata nel delegato utente di un'altra attività, nota come attività padre . Un'attività secondaria può essere scollegata o collegata. Un'attività figlio scollegata è un'attività che viene eseguita indipendentemente dal relativo elemento padre. Un'attività figlio associata è un'attività nidificata creata con l'opzione TaskCreationOptions.AttachedToParent il cui genitore non ne impedisce l'associazione né esplicitamente né per impostazione predefinita. Un'attività può creare un numero qualsiasi di sottoattività attaccate e distaccate, limitate solo dalle risorse di sistema.
Nella tabella seguente sono elencate le differenze di base tra i due tipi di attività figlio.
| Categoria | Attività figlie separate | Attività secondarie collegate |
|---|---|---|
| L'elemento padre attende il completamento dei compiti dei figli. | NO | Sì |
| Padre propaga le eccezioni generate dalle attività figlio. | NO | Sì |
| Lo stato dell'elemento padre dipende dallo stato dell'elemento figlio. | NO | Sì |
Nella maggior parte degli scenari è consigliabile usare attività figlio scollegate, perché le relazioni con altre attività sono meno complesse. Ecco perché le attività create all'interno delle attività padre vengono scollegate per impostazione predefinita ed è necessario specificare in modo esplicito l'opzione TaskCreationOptions.AttachedToParent per creare un'attività figlio associata.
Attività figlie separate
Anche se un'attività figlio viene creata da un'attività padre, per impostazione predefinita è indipendente dall'attività padre. Nell'esempio seguente, un'attività padre crea un'attività figlia semplice. Se si esegue più volte il codice di esempio, è possibile notare che l'output dell'esempio è diverso da quello mostrato e anche che l'output può cambiare ogni volta che si esegue il codice. Ciò si verifica perché l'attività padre e le attività figlio vengono eseguite indipendentemente l'una dall'altra; il figlio è un'attività indipendente. L'esempio attende solo il completamento dell'attività padre e l'attività figlio potrebbe non essere eseguita o completata prima dell'interruzione dell'app console.
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 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'elemento figlio anche se si tratta di un'attività figlio scollegata. La proprietà Result si blocca fino al completamento dell'attività, come illustrato nell'esempio seguente.
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
Attività secondarie collegate
A differenza delle attività figlio scollegate, le attività figlio associate sono strettamente sincronizzate con il compito genitore. È possibile modificare l'attività figlio scollegata nell'esempio precedente in un'attività figlio associata usando l'opzione TaskCreationOptions.AttachedToParent nell'istruzione di creazione dell'attività, come illustrato nell'esempio seguente. In questo codice, l'attività figlio associata viene completata prima del padre. Di conseguenza, l'output dell'esempio è lo stesso ogni volta che si esegue il 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.
È possibile usare le attività figlio associate per creare grafici strettamente sincronizzati di operazioni asincrone.
Tuttavia, un'attività figlia può essere associata al padre solo se il padre non vieta le attività figlie associate. Le attività padre possono impedire esplicitamente alle attività figlio di associarsi, specificando l'opzione TaskCreationOptions.DenyChildAttach nel costruttore della classe dell'attività padre o nel metodo TaskFactory.StartNew. Le attività padre impediscono implicitamente alle attività figlio di associarsi a loro se sono create chiamando il metodo Task.Run. Nell'esempio seguente viene illustrato questo. È identico all'esempio precedente, ad eccezione del fatto che l'attività padre viene creata chiamando il metodo Task.Run(Action) anziché il metodo TaskFactory.StartNew(Action). Poiché l'attività figlio non può collegarsi al suo padre, l'output dell'esempio è imprevedibile. Poiché le opzioni di creazione di attività predefinite per gli overload di 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 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.
Eccezioni nelle attività figli
Se un'attività figlio scollegata genera un'eccezione, l'eccezione deve essere osservata o gestita direttamente nell'attività padre proprio come con qualsiasi altra attività non nidificata. Se un'attività figlio allegata genera un'eccezione, l'eccezione viene propagata automaticamente all'attività padre e ritorna al thread che attende o cerca di accedere alla proprietà Task<TResult>.Result dell'attività. Pertanto, usando le attività figlio associate, è 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. Ovvero, per poter essere annullabile, ogni attività figlio, sia essa collegata o scollegata, deve monitorare lo stato del token di annullamento. Se si desidera annullare un elemento padre e tutti i relativi elementi figlio usando una 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 elementi figlio .
Quando l'elemento padre viene annullato
Se un genitore annulla se stesso prima che inizi l'attività figlio, l'attività figlio non viene mai avviata. Se un'attività genitore viene annullata dopo l'avvio dell'attività figlio, l'attività figlio prosegue fino al completamento, a meno che non abbia la propria logica di annullamento. Per altre informazioni, vedere Annullamento attività .
Quando un'attività figlio scollegata viene annullata
Se un'attività figlio scollegata viene annullata utilizzando lo stesso token passato all'elemento padre e l'elemento padre non attende l'attività figlio, non viene propagata alcuna eccezione, perché l'eccezione viene considerata come annullamento della cooperazione non dannosa. Questo comportamento è uguale a quello di qualsiasi attività di primo livello.
Quando un'attività figlio associata viene annullata
Quando un'attività figlia associata viene annullata usando lo stesso token passato alla relativa attività madre, un TaskCanceledException viene propagato al thread di integrazione all'interno di un AggregateException. È necessario attendere l'attività padre per gestire tutte le eccezioni innocue oltre a tutte le eccezioni di errore propagate attraverso uno schema di attività figlio associate.
Per altre informazioni, vedere Gestione delle Eccezioni.
Impedire l'associazione di un'attività figlia al padre
Un'eccezione non gestita generata da un'attività figlia viene propagata all'attività padre. È possibile usare questo comportamento per osservare tutte le eccezioni di attività figlio da un'attività radice anziché attraversare un albero di attività. Tuttavia, la propagazione delle eccezioni può essere problematica quando un compito padre non prevede associazioni da altro codice. Si consideri, ad esempio, un'app che chiama un componente di 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, eventuali eccezioni non gestite che si verificano nell'attività figlio vengono propagate all'attività padre. Questo potrebbe causare un comportamento imprevisto nell'app principale.
Per impedire a un'attività subordinata di collegarsi all'attività principale, specificare l'opzione TaskCreationOptions.DenyChildAttach quando si crea l'oggetto padre Task o Task<TResult>. Quando un'attività tenta di collegarsi al padre e l'elemento padre specifica l'opzione TaskCreationOptions.DenyChildAttach, l'attività figlio non sarà in grado di collegarsi a un elemento padre e verrà eseguita come se l'opzione TaskCreationOptions.AttachedToParent non fosse specificata.
Potresti anche voler impedire a un'attività secondaria di collegarsi al suo genitore quando non viene completata in modo tempestivo. Poiché un'attività padre non viene completata fino al termine di tutte le attività figlio, un'attività figlio con esecuzione prolungata può causare un'esecuzione insufficiente dell'app complessiva. Per un esempio che illustra come migliorare le prestazioni dell'app impedendo a un'attività di connettersi all'attività padre, vedere Procedura: Impedire a un'attività figlia di connettersi alla relativa attività padre.