Attività di continuazione
Nella programmazione asincrona spesso una determinata operazione asincrona, quando viene completata, richiama un'altra operazione a cui passa dati. A tale scopo in precedenza si utilizzavano i metodi di callback. Nella libreria Task Parallel Library la stessa funzionalità viene fornita dalle attività di continuazione. Un'attività di continuazione (anche nota più semplicemente come continuazione) è un'attività asincrona richiamata da un'altra attività, detta attività precedente, al completamento di quest'ultima.
Benché relativamente facili da utilizzare, le continuazioni sono alquanto potenti e flessibili. Ad esempio, è possibile:
passare dati dall'attività precedente alla continuazione;
specificare le esatte condizioni che devono verificarsi affinché la continuazione venga o meno richiamata;
annullare una continuazione prima che venga avviata o cooperativamente mentre è in esecuzione;
fornire suggerimenti sulle modalità di pianificazione della continuazione;
richiamare più continuazioni dalla stessa attività precedente;
richiamare una determinata continuazione quando tutte o alcune attività precedenti vengono completate;
concatenare continuazioni uno dopo l'altra fino a raggiungere una lunghezza arbitraria;
utilizzare una continuazione per gestire le eccezioni generate dall'attività precedente.
Le continuazioni vengono create tramite il metodo Task.ContinueWith. Nell'esempio seguente viene mostrato il modello di base. Per maggiore chiarezza, viene omessa la gestione delle eccezioni.
' The antecedent task. Can also be created with Task.Factory.StartNew.
Dim taskA As Task(Of DayOfWeek) = New Task(Of DayOfWeek)(Function()
Return DateTime.Today.DayOfWeek
End Function)
' The continuation. Its delegate takes the antecedent task
' as an argument and can return a different type.
Dim continuation As Task(Of String) = taskA.ContinueWith(Function(antecedent)
Return String.Format("Today is {0}", antecedent.Result)
End Function)
' Start the antecedent.
taskA.Start()
' Use the contuation's result.
Console.WriteLine(continuation.Result)
// The antecedent task. Can also be created with Task.Factory.StartNew.
Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);
// The continuation. Its delegate takes the antecedent task
// as an argument and can return a different type.
Task<string> continuation = taskA.ContinueWith((antecedent) =>
{
return String.Format("Today is {0}.",
antecedent.Result);
});
// Start the antecedent.
taskA.Start();
// Use the contuation's result.
Console.WriteLine(continuation.Result);
È inoltre possibile creare una continuazione multiattività che viene eseguita quando alcune o tutte le attività di una matrice vengono completate, come mostrato nell'esempio seguente.
Dim task1 As Task(Of Integer) = New Task(Of Integer)(Function()
' Do some work...
Return 34
End Function)
Dim task2 As Task(Of Integer) = New Task(Of Integer)(Function()
' Do some work...
Return 8
End Function)
Dim tasks() As Task(Of Integer) = {task1, task2}
Dim continuation = Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
Dim answer As Integer = tasks(0).Result + tasks(1).Result
Console.WriteLine("The answer is {0}", answer)
End Sub)
task1.Start()
task2.Start()
continuation.Wait()
Task<int>[] tasks = new Task<int>[2];
tasks[0] = new Task<int>(() =>
{
// Do some work...
return 34;
});
tasks[1] = new Task<int>(() =>
{
// Do some work...
return 8;
});
var continuation = Task.Factory.ContinueWhenAll(
tasks,
(antecedents) =>
{
int answer = tasks[0].Result + tasks[1].Result;
Console.WriteLine("The answer is {0}", answer);
});
tasks[0].Start();
tasks[1].Start();
continuation.Wait();
Una continuazione viene creata nello stato WaitingForActivation e pertanto può essere avviata solo dalla relativa attività precedente. La chiamata di Task.Start su una continuazione nel codice utente comporta la generazione di un oggetto System.InvalidOperationException.
Una continuazione è essa stessa una Task e non blocca il thread da cui viene avviata. Per effettuare il blocco fino al completamento dell'attività di continuazione, utilizzare il metodo Wait.
Opzioni di continuazione
Quando si crea una continuazione ad attività singola è possibile utilizzare un overload ContinueWith che accetta l'enumerazione System.Threading.Tasks.TaskContinuationOptions per specificare le condizioni che devono verificarsi affinché l'attività precedente avvii la continuazione. Ad esempio, è possibile specificare che la continuazione venga eseguita solo se l'esecuzione dell'attività precedente è stata completata oppure solo se è stata completata in uno stato di errore e così via. Se la condizione non è soddisfatta quando l'attività precedente è pronta a richiamare la continuazione, quest'ultima passa direttamente allo stato Canceled e, successivamente, non potrà essere avviata. Se si specifica qualsiasi opzione NotOn oppure OnlyOn con una continuazione multiattività, in fase di esecuzione verrà generata un'eccezione.
L'enumerazione System.Threading.Tasks.TaskContinuationOptions include inoltre le stesse opzioni dell'enumerazione System.Threading.Tasks.TaskCreationOptions. AttachedToParent, LongRunning e PreferFairness presentano gli stessi significati e valori in entrambi i tipi di enumerazione. Queste opzioni possono essere utilizzate con le continuazioni multiattività.
Nella tabella seguente vengono elencati tutti i valori di TaskContinuationOptions.
Elemento |
Descrizione |
---|---|
Specifica il comportamento predefinito quando non è specificato alcun oggetto TaskContinuationOptions. La continuazione verrà pianificata al completamento dell'attività precedente, indipendentemente dallo stato finale di quest'ultima. Se l'attività è un'attività figlio, viene creata come attività annidata disconnessa. |
|
Specifica che la continuazione verrà pianificata in modo che le attività pianificate prima abbiano più possibilità di essere eseguite prima delle attività pianificate in un secondo momento. |
|
Specifica che la continuazione sarà un'operazione a bassa precisione di lunga durata. Fornisce a System.Threading.Tasks.TaskScheduler un'indicazione in merito alla possibilità di dover ricorrere all'oversubscription. |
|
Specifica che la continuazione, se è un'attività figlio, è connessa a un elemento padre della gerarchia delle attività. La continuazione è un'attività figlio solo se anche la relativa attività precedente è un'attività figlio. |
|
Specifica che la continuazione non deve essere pianificata se l'esecuzione della relativa attività precedente è stata completata. |
|
Specifica che la continuazione non deve essere pianificata se la relativa attività precedente ha generato un'eccezione non gestita. |
|
Specifica che la continuazione non deve essere pianificata se la relativa attività precedente è stata annullata. |
|
Specifica che la continuazione deve essere pianificata solo se l'esecuzione della relativa attività precedente è stata completata. |
|
Specifica che la continuazione deve essere pianificata solo se la relativa attività precedente ha generato un'eccezione non gestita. Quando si utilizza l'opzione OnlyOnFaulted, viene garantito che la proprietà Exception dell'attività precedente non sia null. È possibile utilizzare tale proprietà per rilevare l'eccezione e determinare quale eccezione ha comportato errori nell'attività. Se non si accede alla proprietà Exception, l'eccezione non verrà gestita. Inoltre, se si tenta di accedere alla proprietà Result di un'attività che è stata annullata o in cui si sono verificati errori, verrà generata una nuova eccezione. |
|
Specifica che la continuazione deve essere pianificata solo se la relativa attività precedente è stata completata con lo stato Canceled. |
|
Per continuazioni con esecuzione di brevissima durata. Specifica che la continuazione deve essere eseguita idealmente nello stesso thread che comporta la transizione dell'attività precedente allo stato finale. Se al momento della creazione della continuazione l'attività precedente è già completa, il sistema tenterà di eseguire la continuazione nel thread che la crea. Se CancellationTokenSource dell'attività precedente viene collocato in un blocco finally (Finally in Visual Basic), verrà eseguita una continuazione con questa opzione in tale blocco finally. |
Passaggio di dati a una continuazione
Al delegato dell'utente della continuazione viene passato come argomento un riferimento all'attività precedente. Se l'attività precedente è un oggetto System.Threading.Tasks.Task<TResult> e l'esecuzione dell'attività è stata completata, la continuazione può accedere alla proprietà Task<TResult>.Result dell'attività. Quando si utilizzano una continuazione multiattività e il metodo Task.WaitAll, l'argomento è la matrice delle attività precedenti. Quando si utilizza Task.WaitAny, l'argomento è la prima attività precedente completata.
L'oggetto Task<TResult>.Result viene bloccato fino al completamento dell'attività. Tuttavia, se l'attività è stata annullata o presenta errori, Result genera un'eccezione quando il codice tenta di accedervi. Per evitare questo problema è possibile utilizzare l'opzione OnlyOnRanToCompletion, come illustrato nell'esempio seguente.
Dim aTask = Task(Of Integer).Factory.StartNew(Function()
Return 54
End Function)
Dim bTask = aTask.ContinueWith(Sub(antecedent)
Console.WriteLine("continuation {0}", antecedent.Result)
End Sub,
TaskContinuationOptions.OnlyOnRanToCompletion)
var t = Task<int>.Factory.StartNew(() => 54);
var c = t.ContinueWith((antecedent) =>
{
Console.WriteLine("continuation {0}", antecedent.Result);
},
TaskContinuationOptions.OnlyOnRanToCompletion);
Se si desidera che la continuazione venga eseguita anche quando l'esecuzione dell'attività precedente non è stata completata è necessario proteggersi dall'eccezione. Un possibile approccio è testare lo stato dell'attività precedente e tentare di accedere a Result soltanto se lo stato non è Faulted o Canceled. È inoltre possibile esaminare la proprietà Exception dell'attività precedente. Per ulteriori informazioni, vedere Gestione delle eccezioni (Task Parallel Library).
Annullamento di una continuazione
Una continuazione passa allo stato Canceled negli scenari seguenti:
Quando genera un oggetto OperationCanceledException in risposta a una richiesta di annullamento. Analogamente a quanto accade per le altre attività, se l'eccezione contiene lo stesso token passato alla continuazione, essa viene trattata come una conferma di annullamento cooperativo.
Quando alla continuazione viene stato passato un oggetto System.Threading.CancellationToken come argomento e la proprietà IsCancellationRequested del token è true (True) prima dell'esecuzione della continuazione. In questo caso la continuazione non viene avviata e passa direttamente allo stato Canceled.
Quando la continuazione non viene eseguita poiché la condizione impostata nel relativo oggetto TaskContinuationOptions non è stata soddisfatta. Ad esempio, se un'attività passa a uno stato Faulted, la relativa continuazione creata dall'opzione NotOnFaulted passerà allo stato Canceled e non verrà eseguita.
Per impedire che una continuazione venga eseguita se la relativa attività precedente viene annullata, specificare l'opzione NotOnCanceled quando si crea la continuazione.
Se un'attività e la relativa continuazione rappresentano due parti della stessa operazione logica, è possibile passare lo stesso token di annullamento a entrambe le attività, come mostrato nell'esempio seguente.
Dim someCondition As Boolean = True
Dim cts As New CancellationTokenSource
Dim task1 = New Task(Sub()
Dim ct As CancellationToken = cts.Token
While someCondition = True
ct.ThrowIfCancellationRequested()
' Do the work here...
' ...
End While
End Sub,
cts.Token
)
Dim task2 = task1.ContinueWith(Sub(antecedent)
Dim ct As CancellationToken = cts.Token
While someCondition = True
ct.ThrowIfCancellationRequested()
' Do the work here
' ...
End While
End Sub,
cts.Token)
task1.Start()
' ...
' Antecedent and/or continuation will
' respond to this request, depending on when it is made.
cts.Cancel()
Task task = new Task(() =>
{
CancellationToken ct = cts.Token;
while (someCondition)
{
ct.ThrowIfCancellationRequested();
// Do the work.
//...
}
},
cts.Token
);
Task task2 = task.ContinueWith((antecedent) =>
{
CancellationToken ct = cts.Token;
while (someCondition)
{
ct.ThrowIfCancellationRequested();
// Do the work.
//...
}
},
cts.Token);
task.Start();
//...
// Antecedent and/or continuation will
// respond to this request, depending on when it is made.
cts.Cancel();
Se l'attività precedente non è stata annullata, il token può comunque essere utilizzato per annullare la continuazione. Se l'attività precedente è stata annullata, la continuazione non verrà avviata.
Una continuazione passata allo stato Canceled può influire sulle continuazioni successive, a seconda degli oggetti TaskContinuationOptions specificati per tali continuazioni.
Le continuazioni eliminate non verranno avviate.
Continuazioni e attività figlio
Una continuazione viene eseguita solo dopo il completamento dell'attività precedente e di tutte le relative attività figlio connesse. La continuazione non attende il completamento delle attività figlio disconnesse. Lo stato finale dell'attività precedente dipende dallo stato finale delle attività figlio connesse eventualmente presenti. Lo stato delle attività figlio disconnesse non influisce sull'attività padre. Per ulteriori informazioni, vedere Attività annidate e attività figlio.
Gestione delle eccezioni generate dalle continuazioni
Una relazione fra un'attività precedente e una continuazione non è una relazione di tipo padre-figlio. Le eccezioni generate dalle continuazioni non vengono propagate all'attività precedente. Pertanto, come descritto di seguito, le eccezioni generate dalle continuazioni devono essere gestite nello stesso modo in cui verrebbero gestite in qualsiasi altra attività.
- Per restare in attesa della continuazione, utilizzare il metodo Wait, WaitAll o WaitAny o la controparte generica. È possibile restare in attesa di un'attività precedente e delle relative continuazioni nella stessa istruzione try (Try in Visual Basic), come mostrato nell'esempio seguente.
Dim task1 = Task(Of Integer).Factory.StartNew(Function()
Return 54
End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
Console.WriteLine("continuation {0}", antecedent.Result)
Throw New InvalidOperationException()
End Sub)
Try
task1.Wait()
continuation.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
Console.WriteLine("Exception handled. Let's move on.")
var t = Task<int>.Factory.StartNew(() => 54);
var c = t.ContinueWith((antecedent) =>
{
Console.WriteLine("continuation {0}", antecedent.Result);
throw new InvalidOperationException();
});
try
{
t.Wait();
c.Wait();
}
catch (AggregateException ae)
{
foreach(var e in ae.InnerExceptions)
Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
Utilizzare una seconda continuazione per osservare la proprietà Exception della prima continuazione. Per ulteriori informazioni, vedere Gestione delle eccezioni (Task Parallel Library) e Procedura: gestire le eccezioni generate dalle attività.
Se la continuazione è un'attività figlio creata tramite l'opzione AttachedToParent, le relative eccezioni verranno ripropagate dal padre nel thread chiamante, analogamente a quanto accade per qualsiasi altra attività figlio connessa. Per ulteriori informazioni, vedere Attività annidate e attività figlio.
Vedere anche
Concetti
Cronologia delle modifiche
Data |
Cronologia |
Motivo |
---|---|---|
Giugno 2010 |
Aggiunta nota sul comportamento asincrono delle continuazioni. |
Commenti e suggerimenti dei clienti. |