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 usa 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 usare il modello Di inizializzazione delle acquisizioni di risorse (RAII) per assicurarsi che le risorse vengano gestite correttamente quando viene generata un'eccezione nel corpo di un'attività. Per un esempio completo che usa il modello RAII per pulire una risorsa in un'attività annullabile, vedere Procedura dettagliata: Rimozione del lavoro da un thread dell'interfaccia utente.

Punti chiave

In questo documento

Alberi di lavoro paralleli

La libreria PPL utilizza attività e gruppi di attività per gestire attività e calcoli in modo accurato. È possibile annidare gruppi di attività per formare alberi di lavoro parallelo. La figura seguente illustra un albero del lavoro parallelo. In questa illustrazione, tg1 e tg2 rappresentano i gruppi di attività; t1, t2, t3, t4 e t5 rappresentano il lavoro eseguito dai gruppi di attività.

A parallel work tree.

Nell'esempio seguente viene illustrato il codice necessario per creare l'albero dell'illustrazione. In questo esempio e sono oggetti concurrency::structured_task_group; t1, , t3t2t4, , e t5 sono oggetti concurrency::task_handle.tg2tg1

// 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();   
}

È anche possibile usare la classe concurrency::task_group per creare un albero di lavoro simile. La classe concurrency::task supporta anche la nozione di albero del lavoro. Tuttavia, un albero di task è un albero di dipendenza. In un albero di task, i lavori futuri vengono completati dopo il lavoro corrente. In un albero del gruppo di attività, il lavoro interno viene completato prima del lavoro esterno. Per altre informazioni sulle differenze tra attività e gruppi di attività, vedere Parallelismo delle attività.

[Torna all'inizio]

Annullamento di attività parallele

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. Anche se non viene avviato un nuovo lavoro se un'attività o un gruppo di attività viene annullato, il lavoro attivo deve cercare e rispondere all'annullamento.

Per altri esempi che annullano attività parallele, vedere Procedura dettagliata: Connessione using Tasks and XML HTTP Requests, Procedura: Usare l'annullamento per interrompere un ciclo parallelo e Procedura: Usare la gestione delle eccezioni per interrompere un ciclo parallelo.

Uso di un token di annullamento per annullare il lavoro parallelo

Le classi task, task_group e structured_task_group supportano l'annullamento tramite l'utilizzo di token di annullamento. Il PPL definisce le classi concurrency::cancellation_token_source e concurrency::cancellation_token a questo scopo. Quando si usa un token di annullamento per annullare il lavoro, il runtime non avvia nuovo lavoro che sottoscrive tale token. Il lavoro già attivo può usare la funzione membro is_canceled per monitorare il token di annullamento e arrestarlo quando può.

Per avviare l'annullamento, chiamare il metodo concurrency::cancellation_token_source::cancel . È possibile rispondere all'annullamento nei seguenti modi:

  • Per task gli oggetti, usare la funzione concurrency::cancel_current_task . 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 gli algoritmi paralleli, usare la funzione concurrency::is_current_task_group_canceling per rilevare l'annullamento e restituire 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 <concrt.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 (token.is_canceled())
            {
                // 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, pertanto non è necessario uscire in modo esplicito dal ciclo corrente o dalla funzione.

Suggerimento

In alternativa, è possibile chiamare la funzione concurrency::interruption_point anziché cancel_current_task.

È importante chiamare cancel_current_task quando si risponde all'annullamento perché l'attività possa passare allo stato annullato. Se si esce prima di chiamare cancel_current_task, l'operazione passa allo stato completato e tutte le continuazioni basate su valori verranno eseguite.

Attenzione

Non generare mai task_canceled dal codice. In alternativa, chiamare cancel_current_task.

Quando un'attività termina nello stato annullato, il metodo concurrency::task::get genera concurrency::task_canceled. Al contrario, concurrency::task::wait restituisce task_status::canceled e non genera un'eccezione. 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.
*/

Attenzione

Se non si passa un token di annullamento al task costruttore 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 l'utente sceglie un pulsante Annulla nell'interfaccia utente per annullare l'operazione, è possibile disabilitarlo fino a quando l'utente non avvia un'altra operazione. Nell'esempio seguente viene illustrato come usare il metodo concurrency::cancellation_token::register_callback per registrare una funzione di callback eseguita quando viene annullato un token di annullamento.

// 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 Task Parallelism 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.

Attenzione

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

Usare il metodo concurrency::cancellation_token::none quando si chiama un costruttore o una funzione che accetta un cancellation_token oggetto e non si vuole che l'operazione sia annullabile.

È anche possibile fornire un token di annullamento al costruttore di un oggetto task_group o structured_task_group. Un aspetto importante è che i gruppi di attività figlio ereditano il token di annullamento. Per un esempio che illustra questo concetto usando la funzione concurrency::run_with_cancellation_token da eseguire per chiamare parallel_for, vedere Annullamento degli algoritmi paralleli più avanti in questo documento.

[Torna all'inizio]

Token di annullamento e composizione di attività

Le funzioni concurrency::when_all e concurrency::when_any consentono di comporre più attività per implementare modelli comuni. In questa sezione viene descritto il funzionamento di queste funzioni con i token di annullamento.

Quando si specifica un token di annullamento per la when_all funzione e when_any , tale funzione viene annullata solo quando il token di annullamento viene annullato o quando una delle attività partecipante termina in uno stato annullato o 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à del partecipante 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.

[Torna all'inizio]

Utilizzo del metodo cancel per annullare il lavoro parallelo

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 fa sì che i metodi concurrency::task_group::wait e concurrency::structured_task_group::wait restituisca concurrency::canceled.

Se un gruppo di attività viene annullato, le chiamate da ogni attività figlio nel runtime possono attivare un punto di interruzione, che fa sì che il runtime generi e intercetta un tipo di eccezione interna per annullare le attività attive. Il runtime di concorrenza non definisce punti di interruzione specifici; i punti di interruzione possono verificarsi 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 verificata l'annullamento ogni 100iterazione del ciclo di attività. La frequenza di verifica dell'annullamento dipende dalla quantità di lavoro eseguita dall'attività e dalla velocità necessaria alle attività per rispondere all'annullamento.

Se non si ha accesso all'oggetto gruppo di attività padre, chiamare la funzione concurrency::is_current_task_group_canceling per determinare se il gruppo di attività padre è 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 un 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, che è un'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 i structured_task_group::cancel metodi 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.

Attenzione

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 task_group::cancel o structured_task_group::cancel per annullare gli oggetti task eseguiti in un gruppo di attività.

[Torna all'inizio]

Uso di eccezioni per annullare il lavoro parallelo

L'utilizzo dei token di annullamento e del metodo cancel è più efficace della gestione delle eccezioni per annullare un albero del 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 in quanto l'eccezione si propaga verso l'alto. L'argomento Gestione eccezioni illustra in che modo il runtime di concorrenza usa 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'uso del metodo cancel per annullare un lavoro parallelo.

Attenzione

È 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 effettua 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 try-catch blocco per verificare la presenza di annullamento quando il gruppo tg2 di attività attende il completamento delle 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 usata la gestione delle eccezioni per annullare l'intero 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 si riceve alcun valore restituito.

[Torna all'inizio]

Annullamento di algoritmi paralleli

Gli algoritmi paralleli nella libreria PPL, ad esempio parallel_for, si basano sui gruppi di attività. Pertanto, per annullare un algoritmo parallelo, è possibile usare 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 usato 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 il seguente output:

The task group status is: canceled.

Nell'esempio seguente viene usata 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 il seguente output:

Caught 50

Nell'esempio seguente viene usato un flag booleano per coordinare l'annullamento in un ciclo parallel_for. Viene eseguita ogni attività poiché in questo esempio non viene usato 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.

[Torna all'inizio]

Quando non usare l'annullamento

L'uso dell'annullamento è appropriato quando ogni membro di un gruppo di attività correlate può uscire in modo tempestivo. In alcuni scenari, tuttavia, 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. Ciò può causare condizioni di deadlock nell'applicazione. Un altro esempio in cui l'uso dell'annullamento potrebbe non essere appropriato è quello in cui un'attività viene annullata ma la relativa attività figlio esegue un'operazione importante, ad esempio la liberazione di una risorsa. Poiché l'annullamento dell'attività padre determina l'annullamento del set complessivo di attività, tale operazione non verrà eseguita. Per un esempio che illustra questo punto, vedere la sezione Informazioni su come l'annullamento e la gestione delle eccezioni influiscono sulla distruzione degli oggetti nell'argomento Procedure consigliate della libreria di modelli paralleli.

[Torna all'inizio]

Posizione Descrizione
Procedura: Usare l'annullamento per interrompere un ciclo Parallel Illustra come usare l'annullamento per implementare un algoritmo di ricerca parallelo.
Procedura: Usare la gestione delle eccezion per interrompere un ciclo Parallel Viene illustrato come usare la classe task_group per scrivere un algoritmo di ricerca per un albero di base.
Gestione delle eccezioni Descrive il modo in cui il runtime gestisce le eccezioni generate dai gruppi di attività, dalle attività leggere e dagli agenti asincroni e come rispondere alle eccezioni nelle applicazioni.
Parallelismo delle attività Descrive il modo in cui le attività vengono correlate ai gruppi di attività e come usare le attività strutturate e non strutturate nelle applicazioni.
Algoritmi paralleli Descrive gli algoritmi paralleli per svolgere simultaneamente il lavoro sulle raccolte di dati.
PPL (Parallel Patterns Library) Fornisce una panoramica della libreria PPL.

Riferimento

Classe task (runtime di concorrenza)

Classe cancellation_token_source

Classe cancellation_token

Classe task_group

Classe structured_task_group

Funzione parallel_for