Annullamento nella libreria PPL
In questo documento viene illustrato il ruolo dell'annullamento nella libreria PPL (Parallel Patterns Library), come annullare un lavoro parallelo e come determinare quando un lavoro parallelo è annullato.
Nota
Il runtime utilizza la gestione delle eccezioni per implementare l'annullamento.Non rilevare o gestire queste eccezioni nel codice.Inoltre, si consiglia di scrivere codice indipendente dalle eccezioni nei corpi delle funzioni per le attività.Ad esempio, è possibile utilizzare il modello Resource Acquisition Is Initialization (RAII) per assicurarsi che le risorse vengono gestite correttamente quando viene generata un'eccezione nel corpo di un'attività.Per un esempio completo che utilizza il modello RAII per eseguire la pulizia di una risorsa in un'attività annullabile, vedere Procedura dettagliata: rimozione di lavoro da un thread dell'interfaccia utente.
L'annullamento è cooperativo e include un coordinamento tra il codice che richiede l'annullamento e l'attività che risponde all'annullamento.
Quando possibile, utilizzare i token di annullamento per annullare un lavoro. La classe concurrency::cancellation_token definisce un token di annullamento.
Quando si utilizzano i token di annullamento, utilizzare il metodo concurrency::cancellation_token_source::cancel per avviare l'annullamento e le funzioni concurrency::cancel_current_task e concurrency::is_task_cancellation_requested per rispondere all'annullamento.
L'annullamento non si verifica immediatamente. Sebbene un nuovo lavoro non venga avviato se un'attività o un gruppo di attività viene annullato, il lavoro attivo deve controllare e rispondere all'annullamento.
Una continuazione basata su valori eredita il token di annullamento dell'attività precedente. Una continuazione basata su attività non eredita mai il token dell'attività precedente.
Utilizzare il metodo concurrency::cancellation_token::none quando si deve chiamare un costruttore o una funzione che accetta un oggetto cancellation_token ma non si desidera che l'operazione sia annullabile. Inoltre, se non si passa un token di annullamento al costruttore di concurrency::task o alla funzione concurrency::create_task, tale attività non è annullabile.
Strutture ad albero del lavoro parallelo
Annullamento delle attività in parallelo
Utilizzo di un token di annullamento per annullare un lavoro parallelo
Utilizzo del metodo cancel per annullare un lavoro parallelo
Utilizzo delle eccezioni per annullare un lavoro parallelo
Annullamento degli algoritmi paralleli
Casi i cui non è consigliabile utilizzare l'annullamento
La libreria PPL utilizza attività e gruppi di attività per gestire attività e calcoli in modo accurato. È possibile annidare i gruppi di attività in modo formare strutture ad albero del lavoro parallelo. Nella figura seguente viene illustrata una struttura ad albero del lavoro parallelo. In questa illustrazione, tg1 e tg2 rappresentano i gruppi di attività; t1, t2, t3, t4 e t5 rappresentano il lavoro che i gruppi di attività eseguono.
Nell'esempio seguente viene illustrato il codice necessario per creare la struttura ad albero dell'illustrazione. In questo esempio tg1 e tg2 sono oggetti concurrency::structured_task_group, mentre t1, t2, t3, t4 e t5 sono oggetti concurrency::task_handle.
// task-tree.cpp
// compile with: /c /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
void create_task_tree()
{
// Create a task group that serves as the root of the tree.
structured_task_group tg1;
// Create a task that contains a nested task group.
auto t1 = make_task([&] {
structured_task_group tg2;
// Create a child task.
auto t4 = make_task([&] {
// TODO: Perform work here.
});
// Create a child task.
auto t5 = make_task([&] {
// TODO: Perform work here.
});
// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();
});
// Create a child task.
auto t2 = make_task([&] {
// TODO: Perform work here.
});
// Create a child task.
auto t3 = make_task([&] {
// TODO: Perform work here.
});
// Run the child tasks and wait for them to finish.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);
tg1.wait();
}
È inoltre possibile utilizzare la classe concurrency::task_group per creare qualcosa di simile a una struttura ad albero di lavoro. La classe concurrency::task supporta inoltre la nozione di struttura ad albero di lavoro. Tuttavia, una struttura ad albero di task è un albero di dipendenza. In una struttura ad albero di task, i lavori futuri vengono completati dopo il lavoro corrente. In una struttura ad albero del gruppo di attività, il lavoro interno viene completato prima del lavoro esterno. Per ulteriori informazioni sulle differenze tra attività e gruppi di attività, vedere Parallelismo delle attività (runtime di concorrenza).
[Top]
Sono disponibili più modi per annullare un lavoro parallelo. La modalità consigliata è quella che consiste nell'utilizzo di un token di annullamento. I gruppi di attività supportano anche il metodo concurrency::task_group::cancel e il metodo concurrency::structured_task_group::cancel. L'ultimo modo consiste nel generare un'eccezione nel corpo di una funzione lavoro dell'attività. Indipendentemente dal metodo scelto, si tenga presente che l'annullamento non si verifica immediatamente. Sebbene un nuovo lavoro non venga avviato se un'attività o un gruppo di attività viene annullato, il lavoro attivo deve controllare e rispondere all'annullamento.
Per ulteriori esempi in cui vengono annullate le attività in parallelo, vedere Procedura dettagliata: connessione tramite attività e richieste HTTP XML, Procedura: utilizzare l'annullamento per interrompere un ciclo Parallel e Procedura: utilizzare la gestione delle eccezion per interrompere un ciclo Parallel.
Le classi task, task_group e structured_task_group supportano l'annullamento tramite l'utilizzo di token di annullamento. La libreria PPL definisce le classi concurrency::cancellation_token e concurrency::cancellation_token_source a questo scopo. Quando si utilizza un token di annullamento per annullare il lavoro il runtime non avvia il lavoro che sovrascrive il token. Il lavoro che è già attivo può monitorare, annullare i token e quando possibile arrestarli.
Per avviare l'annullamento, chiamare il metodo concurrency::cancellation_token_source::cancel. È possibile rispondere all'annullamento nei seguenti modi:
Per gli oggetti task, utilizzare le funzioni concurrency::cancel_current_task e concurrency::is_task_cancellation_requested. cancel_current_task annulla l'attività corrente e tutte le relative continuazioni basate su valori. (Non annulla il token di annullamento associato all'attività o alle relative continuazioni.)
Per i gruppi di attività e per gli algoritmi paralleli, utilizzare la funzione concurrency::is_current_task_group_canceling per rilevare l'annullamento e per ritornare il prima possibile dal corpo dell'attività quando questa funzione restituisce true. (Non chiamare cancel_current_task da un gruppo di attività.)
Nell'esempio seguente viene illustrato il primo modello di base per l'annullamento delle attività. Il corpo dell'attività controlla occasionalmente l'annullamento all'interno di un ciclo.
// task-basic-cancellation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
bool do_work()
{
// Simulate work.
wcout << L"Performing work..." << endl;
wait(250);
return true;
}
int wmain()
{
cancellation_token_source cts;
auto token = cts.get_token();
wcout << L"Creating task..." << endl;
// Create a task that performs work until it is canceled.
auto t = create_task([]
{
bool moreToDo = true;
while (moreToDo)
{
// Check for cancellation.
if (is_task_cancellation_requested())
{
// TODO: Perform any necessary cleanup here...
// Cancel the current task.
cancel_current_task();
}
else
{
// Perform work.
moreToDo = do_work();
}
}
}, token);
// Wait for one second and then cancel the task.
wait(1000);
wcout << L"Canceling task..." << endl;
cts.cancel();
// Wait for the task to cancel.
wcout << L"Waiting for task to complete..." << endl;
t.wait();
wcout << L"Done." << endl;
}
/* Sample output:
Creating task...
Performing work...
Performing work...
Performing work...
Performing work...
Canceling task...
Waiting for task to complete...
Done.
*/
La funzione cancel_current_task genera un'eccezione; non è pertanto necessario uscire in modo esplicito dal ciclo corrente o dalla funzione.
Suggerimento
In alternativa, è possibile chiamare la funzione concurrency::interruption_point anziché is_task_cancellation_requested e cancel_current_task.
È importante chiamare cancel_current_task quando si risponde all'annullamento perché l'attività possa passare allo stato annullato. Se si ritorna prima di chiamare cancel_current_task, l'operazione passa allo stato completato e tutte le continuazioni basate su valori verranno eseguite.
Avviso
Non generare mai task_canceled dal codice.Chiamare cancel_current_task.
Quando un'attività termina nello stato annullato, il metodo concurrency::task::get genera concurrency::task_canceled. (Viceversa, concurrency::task::wait restituisce task_status::canceled e non genera eccezioni.) Nell'esempio seguente viene illustrato questo comportamento per una continuazione basata su attività. Una continuazione basata su attività viene sempre chiamata, anche quando l'attività precedente è stata annullata.
// task-canceled.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
auto t1 = create_task([]() -> int
{
// Cancel the task.
cancel_current_task();
});
// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](task<int> t)
{
try
{
int n = t.get();
wcout << L"The previous task returned " << n << L'.' << endl;
}
catch (const task_canceled& e)
{
wcout << L"The previous task was canceled." << endl;
}
});
// Wait for all tasks to complete.
t2.wait();
}
/* Output:
The previous task was canceled.
*/
Poiché le continuazioni basate su valori ereditano il token della relativa attività precedente a meno che non vengano create con un token esplicito, le continuazioni entrano immediatamente nello stato annullato anche quando l'attività precedente è ancora in esecuzione. Pertanto, qualsiasi eccezione generata dall'attività precedente dopo l'annullamento non verrà propagata alle attività di continuazione. Lo stato dell'attività precedente viene sempre sottoposto a override dall'annullamento. L'esempio seguente è simile al precedente, ma illustra il comportamento per una continuazione basata su valori.
auto t1 = create_task([]() -> int
{
// Cancel the task.
cancel_current_task();
});
// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
wcout << L"The previous task returned " << n << L'.' << endl;
});
try
{
// Wait for all tasks to complete.
t2.get();
}
catch (const task_canceled& e)
{
wcout << L"The task was canceled." << endl;
}
/* Output:
The task was canceled.
*/
Avviso
Se non si passa un token di annullamento al costruttore di task o alla funzione concurrency::create_task, tale attività non è annullabile.Inoltre, è necessario passare lo stesso token di annullamento al costruttore di tutte le attività annidate (ovvero alle attività create nel corpo di un'altra attività) per annullare contemporaneamente tutte le attività.
È possibile eseguire codice arbitrario quando un token di annullamento viene annullato. Ad esempio, se un utente sceglie un pulsante Annulla sull'interfaccia utente per annullare l'operazione, è possibile disabilitare il pulsante finché l'utente non avvia un'altra operazione. Nell'esempio seguente viene illustrato come utilizzare il metodo concurrency::cancellation_token::register_callback per registrare una funzione di callback che viene eseguita quando un token di annullamento viene annullato.
// task-cancellation-callback.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
cancellation_token_source cts;
auto token = cts.get_token();
// An event that is set in the cancellation callback.
event e;
cancellation_token_registration cookie;
cookie = token.register_callback([&e, token, &cookie]()
{
wcout << L"In cancellation callback..." << endl;
e.set();
// Although not required, demonstrate how to unregister
// the callback.
token.deregister_callback(cookie);
});
wcout << L"Creating task..." << endl;
// Create a task that waits to be canceled.
auto t = create_task([&e]
{
e.wait();
}, token);
// Cancel the task.
wcout << L"Canceling task..." << endl;
cts.cancel();
// Wait for the task to cancel.
t.wait();
wcout << L"Done." << endl;
}
/* Sample output:
Creating task...
Canceling task...
In cancellation callback...
Done.
*/
Il documento Parallelismo delle attività (runtime di concorrenza) illustra la differenza tra le continuazioni basate su valori e basate su attività. Se non si fornisce un oggetto cancellation_token a un'attività di continuazione, la continuazione eredita il token di annullamento dall'attività precedente nei modi seguenti:
Una continuazione basata su valori eredita sempre il token di annullamento dell'attività precedente.
Una continuazione basata su attività non eredita mai il token di annullamento dell'attività precedente. L'unico modo per rendere una continuazione basata su attività annullabile è quello di passarle esplicitamente un token di annullamento.
Questi comportamenti non sono influenzati da un'attività in cui si è verificato un errore (ovvero una che ha generato un'eccezione). In questo caso, una continuazione basata su valori viene annullata; una continuazione basata su attività non viene annullata.
Avviso
Un'attività creata in un'altra attività (ovvero un'attività annidata) non eredita il token di annullamento dell'attività padre.Solo una continuazione basata su valori eredita il token di annullamento dell'attività precedente.
Suggerimento
Utilizzare il metodo concurrency::cancellation_token::none quando si deve chiamare un costruttore o una funzione che accetta un oggetto cancellation_token e non si desidera che l'operazione sia annullabile.
È anche possibile fornire un token di annullamento al costruttore di un oggetto structured_task_group o task_group. Un aspetto importante di questo fatto è che i gruppi di attività figlio ereditano il token di annullamento. Per un esempio in cui venga illustrato questo concetto utilizzando la funzione concurrency::run_with_cancellation_token per chiamare parallel_for, vedere Annullamento degli algoritmi paralleli più avanti in questo documento.
[Top]
Le funzioni concurrency::when_all e concurrency::when_any sono utili per creare più attività per implementare modelli comuni. In questa sezione viene descritto il funzionamento di queste funzioni con i token di annullamento.
Quando si fornisce un token di annullamento alla funzione when_any o a when_all, quella funzione si annulla solo quando il token di annullamento viene annullato o quando una delle attività partecipanti termina in uno stato annullato oppure genera un'eccezione.
La funzione when_all eredita il token di annullamento da ogni attività che costituisce l'operazione globale quando non viene fornito un token di annullamento per essa. L'attività restituita da when_all viene annullata quando uno di questi token viene annullato e almeno una delle attività partecipanti non è ancora stata avviata o è in esecuzione. Un comportamento simile si verifica quando una delle attività genera un'eccezione – l'attività restituita da when_all viene immediatamente annullata con tale eccezione.
Il runtime sceglie il token di annullamento per l'attività restituita dalla funzione when_any quando l'attività è stata completata. Se nessuna delle attività partecipanti termina in uno stato completato e una o più attività generano un'eccezione, una delle attività che ha generato un'eccezione viene scelta per completare when_any e il relativo token viene scelto come il token per l'attività finale. Se più di un'attività termina nello stato completato, l'attività restituita da when_any termina in uno stato completato. Il runtime tenta di selezionare un'attività completata il cui token non è stato annullato in caso di completamento così l'attività restituita da when_any non verrà immediatamente annullata sebbene altre attività in esecuzione possano essere completate in un momento successivo.
[Top]
I metodi concurrency::task_group::cancel e concurrency::structured_task_group::cancel impostano un gruppo di attività sullo stato annullato. Dopo avere chiamato cancel, il gruppo di attività non avvia attività successive. I metodi cancel possono essere chiamati da più attività figlio. Un'attività annullata determina la restituzione di concurrency::canceled da parte dei metodi concurrency::task_group::wait e concurrency::structured_task_group::wait.
Se un gruppo di attività viene annullato, le chiamate da ogni attività figlio nel runtime possono attivare un punto di interruzione e il runtime genererà e rileverà un tipo di eccezione interno per annullare le attività attive. Il runtime di concorrenza non definisce punti di interruzione specifici; si possono verificare in qualsiasi chiamata al runtime. Il runtime deve gestire le eccezioni generate per poter eseguire l'annullamento. Pertanto, non gestire le eccezioni sconosciute nel corpo di un'attività.
Se un'attività figlio esegue un'operazione che richiede molto tempo e non viene chiamata nel runtime, deve verificare periodicamente l'annullamento e uscire in modo tempestivo. Nell'esempio seguente viene illustrato un modo per determinare l'annullamento di un lavoro. L'attività t4 annulla il gruppo di attività padre quando rileva un errore. L'attività t5 chiama occasionalmente il metodo structured_task_group::is_canceling per verificare l'annullamento. Se il gruppo di attività padre è annullato, l'attività t5 visualizza un messaggio e viene chiusa.
structured_task_group tg2;
// Create a child task.
auto t4 = make_task([&] {
// Perform work in a loop.
for (int i = 0; i < 1000; ++i)
{
// Call a function to perform work.
// If the work function fails, cancel the parent task
// and break from the loop.
bool succeeded = work(i);
if (!succeeded)
{
tg2.cancel();
break;
}
}
});
// Create a child task.
auto t5 = make_task([&] {
// Perform work in a loop.
for (int i = 0; i < 1000; ++i)
{
// To reduce overhead, occasionally check for
// cancelation.
if ((i%100) == 0)
{
if (tg2.is_canceling())
{
wcout << L"The task was canceled." << endl;
break;
}
}
// TODO: Perform work here.
}
});
// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();
In questo esempio viene verificato l'annullamento ogni 100iterazioni del ciclo dell'attività. La frequenza con cui viene verificato l'annullamento dipende dalla quantità di lavoro eseguita dall'attività e dalla velocità necessaria alle attività per rispondere all'annullamento.
Se non si dispone dell'accesso all'oggetto gruppo di attività padre, chiamare la funzione concurrency::is_current_task_group_canceling per determinare se il gruppo di attività padre è stato annullato.
Il metodo cancel influisce solo sulle attività figlio. Se, ad esempio, si annulla il gruppo di attività tg1 nell'illustrazione della struttura ad albero del lavoro parallelo, saranno interessate tutte le attività della struttura ad albero (t1, t2, t3, t4 e t5). Se si annulla il gruppo di attività annidato, tg2, saranno interessate solo le attività t4 e t5.
Quando si chiama il metodo cancel, vengono annullati anche tutti i gruppi di attività figlio. Tuttavia, l'annullamento non influisce sugli elementi padre del gruppo di attività di una struttura ad albero del lavoro parallelo. Negli esempi seguenti viene illustrata tale condizione in base all'illustrazione della struttura ad albero del lavoro parallelo.
Nel primo di questi esempi viene creata una funzione lavoro per l'attività t4, attività figlio del gruppo di attività tg2. La funzione lavoro chiama la funzione work in un ciclo. Se una chiamata a work non riesce, l'attività annulla il relativo gruppo di attività padre, determinando il passaggio allo stato annullato del gruppo di attività tg2 ma senza annullare il gruppo di attività tg1.
auto t4 = make_task([&] {
// Perform work in a loop.
for (int i = 0; i < 1000; ++i)
{
// Call a function to perform work.
// If the work function fails, cancel the parent task
// and break from the loop.
bool succeeded = work(i);
if (!succeeded)
{
tg2.cancel();
break;
}
}
});
Il secondo esempio è simile al primo, ad eccezione del fatto che l'attività annulla il gruppo di attività tg1. Questa operazione ha effetto su tutte le attività della struttura ad albero (t1, t2, t3, t4 e t5).
auto t4 = make_task([&] {
// Perform work in a loop.
for (int i = 0; i < 1000; ++i)
{
// Call a function to perform work.
// If the work function fails, cancel all tasks in the tree.
bool succeeded = work(i);
if (!succeeded)
{
tg1.cancel();
break;
}
}
});
La classe structured_task_group non è thread-safe. Pertanto, un'attività figlio che chiama un metodo del relativo oggetto structured_task_group padre produce un comportamento non specificato. Le eccezioni a questa regola sono rappresentate dai metodi structured_task_group::cancel e concurrency::structured_task_group::is_canceling. Un'attività figlio può chiamare questi metodi per annullare il gruppo di attività padre o verificarne l'annullamento.
Avviso
Sebbene sia possibile utilizzare un token di annullamento per annullare il lavoro eseguito da un gruppo di attività che viene eseguito come figlio di un oggetto task, non è possibile utilizzare i metodi structured_task_group::cancel o task_group::cancel per annullare gli oggetti task eseguiti in un gruppo di attività.
[Top]
L'utilizzo dei token di annullamento e del metodo cancel è più efficace della gestione delle eccezioni per annullare una struttura ad albero di lavoro parallelo. I token di annullamento e il metodo cancel annullano un'attività e tutte le attività figlio dall'alto verso il basso. La gestione delle eccezioni funziona invece in ordine sequenziale dal basso verso l'alto e deve annullare ogni gruppo di attività figlio in modo indipendente poiché l'eccezione si propaga verso l'alto. Nell'argomento Gestione delle eccezioni nel runtime di concorrenza viene illustrato il modo in cui il runtime di concorrenza utilizza le eccezioni per comunicare gli errori. Tuttavia, non tutte le eccezioni indicano un errore. Un algoritmo di ricerca potrebbe, ad esempio, annullare l'attività associata quando viene trovato il risultato. Tuttavia, come indicato in precedenza, la gestione delle eccezioni è meno efficiente dell'utilizzo del metodo cancel per annullare un lavoro parallelo.
Avviso
È consigliabile utilizzare le eccezioni per annullare un lavoro parallelo solo se necessario.I token di annullamento e i metodi cancel del gruppo di attività sono più efficienti e meno soggetti ad errori.
Quando si genera un'eccezione nel corpo di una funzione lavoro passata a un gruppo di attività, il runtime archivia l'eccezione e ne esegue il marshalling nel contesto in attesa del completamento del gruppo di attività. Analogamente al metodo cancel, il runtime elimina tutte le attività non ancora avviate e non accetta nuove attività.
Il terzo esempio è simile al secondo, ad eccezione del fatto che l'attività t4 genera un'eccezione per annullare il gruppo di attività tg2. In questo esempio viene utilizzato un blocco try-catch per verificare l'annullamento quando il gruppo di attività tg2 attende il completamento delle relative attività figlio. Analogamente al primo esempio, viene determinato il passaggio allo stato annullato del gruppo di attività tg2 ma senza annullare il gruppo di attività tg1.
structured_task_group tg2;
// Create a child task.
auto t4 = make_task([&] {
// Perform work in a loop.
for (int i = 0; i < 1000; ++i)
{
// Call a function to perform work.
// If the work function fails, throw an exception to
// cancel the parent task.
bool succeeded = work(i);
if (!succeeded)
{
throw exception("The task failed");
}
}
});
// Create a child task.
auto t5 = make_task([&] {
// TODO: Perform work here.
});
// Run the child tasks.
tg2.run(t4);
tg2.run(t5);
// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
tg2.wait();
}
catch (const exception& e)
{
wcout << e.what() << endl;
}
Nel quarto esempio viene utilizzata la gestione delle eccezioni per annullare l'intera struttura ad albero del lavoro. In questo esempio l'eccezione viene rilevata quando il gruppo di attività tg1 attende il completamento delle relative attività figlio anziché quando il gruppo di attività tg2 attende le relative attività figlio. Analogamente al secondo esempio, questa condizione determina il passaggio allo stato annullato di entrambi i gruppi di attività della struttura ad albero, tg1 e tg2.
// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);
// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
tg1.wait();
}
catch (const exception& e)
{
wcout << e.what() << endl;
}
Poiché i metodi task_group::wait e structured_task_group::wait vengono generati quando un'attività figlio genera un'eccezione, non viene ricevuto alcun valore restituito.
[Top]
Gli algoritmi paralleli nella libreria PPL, ad esempio parallel_for, si basano sui gruppi di attività. Pertanto, per annullare un algoritmo parallelo, è possibile utilizzare molte delle stesse tecniche.
Negli esempi seguenti vengono illustrati diversi modi per annullare un algoritmo parallelo.
Nell'esempio riportato di seguito si utilizza la funzione run_with_cancellation_token per chiamare l'algoritmo parallel_for. La funzione run_with_cancellation_token accetta un token di annullamento come argomento e chiama la funzione lavoro fornita in modo sincrono. Poiché gli algoritmi paralleli si basano sulle attività, questi ereditano il token di annullamento dell'attività padre. Pertanto, parallel_for può rispondere all'annullamento.
// cancel-parallel-for.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Call parallel_for in the context of a cancellation token.
cancellation_token_source cts;
run_with_cancellation_token([&cts]()
{
// Print values to the console in parallel.
parallel_for(0, 20, [&cts](int n)
{
// For demonstration, cancel the overall operation
// when n equals 11.
if (n == 11)
{
cts.cancel();
}
// Otherwise, print the value.
else
{
wstringstream ss;
ss << n << endl;
wcout << ss.str();
}
});
}, cts.get_token());
}
/* Sample output:
15
16
17
10
0
18
5
*/
Nell'esempio seguente viene utilizzato il metodo concurrency::structured_task_group::run_and_wait per chiamare l'algoritmo parallel_for. Il metodo structured_task_group::run_and_wait attende il completamento dell'attività fornita. L'oggetto structured_task_group consente alla funzione lavoro di annullare l'attività.
// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;
task_group_status status = tg.run_and_wait([&] {
parallel_for(0, 100, [&](int i) {
// Cancel the task when i is 50.
if (i == 50)
{
tg.cancel();
}
else
{
// TODO: Perform work here.
}
});
});
// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
wcout << L"not complete." << endl;
break;
case completed:
wcout << L"completed." << endl;
break;
case canceled:
wcout << L"canceled." << endl;
break;
default:
wcout << L"unknown." << endl;
break;
}
Questo esempio produce l'output che segue.
Nell'esempio seguente viene utilizzata la gestione delle eccezioni per annullare un ciclo parallel_for. Il runtime effettua il marshalling dell'eccezione nel contesto di chiamata.
try
{
parallel_for(0, 100, [&](int i) {
// Throw an exception to cancel the task when i is 50.
if (i == 50)
{
throw i;
}
else
{
// TODO: Perform work here.
}
});
}
catch (int n)
{
wcout << L"Caught " << n << endl;
}
Questo esempio produce l'output che segue.
Nell'esempio seguente viene utilizzato un flag booleano per coordinare l'annullamento in un ciclo parallel_for. Viene eseguita ogni attività poiché in questo esempio non viene utilizzato il metodo cancel o la gestione delle eccezioni per annullare il set complessivo di attività. Pertanto, questa tecnica può comportare un sovraccarico maggiore nell'elaborazione rispetto a un meccanismo di annullamento.
// Create a Boolean flag to coordinate cancelation.
bool canceled = false;
parallel_for(0, 100, [&](int i) {
// For illustration, set the flag to cancel the task when i is 50.
if (i == 50)
{
canceled = true;
}
// Perform work if the task is not canceled.
if (!canceled)
{
// TODO: Perform work here.
}
});
Ogni metodo di annullamento presenta alcuni vantaggi rispetto agli altri. Scegliere il metodo appropriato alle specifiche esigenze.
[Top]
L'utilizzo dell'annullamento è appropriato quando ogni membro di un gruppo di attività correlate può uscire in modo tempestivo. Esistono tuttavia alcuni scenari in cui l'annullamento potrebbe non essere appropriato per l'applicazione. Ad esempio, poiché l'annullamento delle attività è cooperativo, il set complessivo di attività non verrà annullato se un singola attività è bloccata. Se, ad esempio, un'attività non è ancora stata avviata, ma sblocca un'altra attività attiva, non verrà avviata se il gruppo di attività viene annullato. In questo modo, possono verificarsi condizioni di deadlock nell'applicazione. Un altro esempio in cui l'utilizzo dell'annullamento potrebbe non essere appropriato è quando un'attività viene annullata ma la relativa attività figlio esegue un'operazione importante, ad esempio la liberazione di una risorsa. Poiché quando l'attività padre viene annullata viene annullato il set complessivo di attività, tale operazione non verrà eseguita. Per un esempio in cui viene illustrato questo punto, vedere la sezione Come l'annullamento e la gestione delle eccezioni influiscono sull'eliminazione degli oggetti nelle procedure consigliate nell'argomento relativo alla libreria PPL.
[Top]
Titolo |
Descrizione |
---|---|
Procedura: utilizzare l'annullamento per interrompere un ciclo Parallel |
Viene illustrato come utilizzare l'annullamento per implementare un algoritmo di ricerca parallelo. |
Procedura: utilizzare la gestione delle eccezion per interrompere un ciclo Parallel |
Viene illustrato come utilizzare la classe task_group per scrivere un algoritmo di ricerca per una struttura ad albero di base. |
Viene descritto come il runtime gestisce le eccezioni generate dai gruppi di attività, dalle attività leggere e dagli agenti asincroni e come rispondere alle eccezioni nelle applicazioni. |
|
Viene descritto il modo in cui le attività vengono correlate ai gruppi di attività e come utilizzare le attività strutturate e non strutturate nelle applicazioni. |
|
Vengono descritti gli algoritmi paralleli per svolgere simultaneamente il lavoro sulle raccolte di dati. |
|
Vengono forniti i cenni preliminari sulla libreria PPL. |
Classe task (runtime di concorrenza)