Condividi tramite


Annullamento nella libreria PPL

In questo documento viene illustrato il ruolo dell'annullamento nella libreria (PPL) PPL, come annullare un lavoro parallelo e come determinare quando il lavoro parallelo viene 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.

Punti chiave

  • L'annullamento è cooperativo e include la coordinazione del codice richiesto l'annullamento e l'attività che risponde all'annullamento.

  • Quando possibile, token di annullamento di utilizzo per annullare 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 il lavoro non sia avviato se un'attività o un gruppo di attività viene annullato, il lavoro attivo deve controllare e per rispondere all'annullamento.

  • Una continuazione basata su valore eredita il token di annullamento della relativa attività precedente.Una continuazione relativa alle attività non eredita mai il token della relativa attività precedente.

  • Utilizzare il metodo concurrency::cancellation_token::none quando si chiama un costruttore o eseguono che accetta un oggetto cancellation_token ma non si desidera che l'operazione sia cancellable.Inoltre, se non si passa un token di annullamento al costruttore concurrency::task o alla funzione concurrency::create_task, tale attività non è cancellable.

In questo documento

  • Strutture ad albero del lavoro parallelo

  • Annullamento delle attività in parallelo

    • Utilizzando 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

Strutture ad albero del lavoro parallelo

La libreria PPL utilizza le attività e i gruppi di attività gestire le attività con granularità fine e i calcoli.È 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, t4e t5 rappresentano il lavoro che i gruppi di attività eseguono.

Struttura ad albero del lavoro parallelo

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 ; t1, t2, t3, t4e 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 un simile albero del lavoro.La classe concurrency::task supporta inoltre la nozione di un albero di lavoro.Tuttavia, un albero task è una struttura ad albero di dipendenza.In una struttura ad albero task, il lavoro futuro dopo aver attività in corso.In una struttura ad albero del gruppo di attività, il funzionamento interno completata prima di lavoro esterno.Per ulteriori informazioni sulle differenze tra attività e gruppi di attività, vedere Parallelismo delle attività (runtime di concorrenza).

[]Parte superiore

Annullamento delle attività in parallelo

Esistono diversi modi per annullare il lavoro parallelo.La modalità consigliata consiste nell'utilizzare un token di annullamento.I gruppi di attività supportano anche il metodo concurrency::task_group::cancel e il metodo concurrency::structured_task_group::cancel.La modalità finale consiste nel generare un'eccezione nel corpo di una funzione lavoro di attività.Indipendentemente dal metodo che si sceglie, riconosce che l'annullamento non si verifica immediatamente.Sebbene il lavoro non sia avviato se un'attività o un gruppo di attività viene annullato, il lavoro attivo deve controllare e per rispondere all'annullamento.

Per ulteriori esempi che annullano le attività in parallelo, vedere Procedura dettagliata: connessione tramite attività e richiesta HTTP XML (IXHR2), Procedura: utilizzare l'annullamento per interrompere un ciclo Parallele Procedura: utilizzare la gestione delle eccezion per interrompere un ciclo Parallel.

Dd984117.collapse_all(it-it,VS.110).gifUtilizzando un token di annullamento per annullare un lavoro parallelo

task, task_groupe le classi 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 sottoscrivere il token.Funzioni che è già attiva può monitorare il relativo token di annullamento e arrestandosi quando possibile.

Per avviare l'annullamento, chiamare il metodo concurrency::cancellation_token_source::cancel.La risposta 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 le relative alle continuazioni basate su valore.(Non annulla il token di annullamento associato all'attività o le continuazioni.)

  • Per i gruppi di attività e algoritmi paralleli, utilizzare la funzione concurrency::is_current_task_group_canceling per rilevare l'annullamento e restituire il prima possibile nel corpo di un'attività quando la funzione restituisce true.(Non chiamare cancel_current_task da un gruppo di attività.)

L'esempio seguente mostra il primo modello di base per l'annullamento delle attività.I controlli del corpo di un'attività occasionalmente per l'annullamento in 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.
*/

Generata l'eccezione di funzione cancel_current_task ; pertanto, non è necessario impostare in modo esplicito che il ciclo corrente o eseguire.

SuggerimentoSuggerimento

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

È importante chiamare cancel_current_task quando la risposta all'annullamento perché transizioni l'attività allo stato canceled.Se in precedenza anziché chiamare cancel_current_task, le transizioni di un'operazione allo stato completato e a tutte le continuazioni basate su valore vengono eseguite.

Nota di avvisoAttenzione

Non generano mai task_canceled dal codice.Chiamata cancel_current_task anziché.

Alla fine di un'attività lo stato annullato, il metodo concurrency::task::get generato concurrency::task_canceled.(Viceversa, concurrency::task::wait restituisce task_status::canceled e non verrà generato.) Nell'esempio seguente viene illustrato questo comportamento per una continuazione relativa alle attività.Una continuazione basata su attività è detta sempre, anche quando l'attività precedente viene 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é a continuazioni basate su valore ereditano il token della relativa attività precedente a meno che non vengano create con un token esplicito, le continuazioni immediatamente immettere lo stato annullato anche quando l'attività precedente è ancora in esecuzione.Pertanto, qualsiasi eccezione generata dall'attività precedente dopo l'annullamento non viene propagato alle attività di continuazione.L'annullamento esegue sempre l'override dello stato dell'attività precedente.L'esempio seguente è simile al precedente, ma viene illustrato il comportamento per una continuazione basata su valore.

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.
*/
Nota di avvisoAttenzione

Se non si passa un token di annullamento al costruttore task o alla funzione concurrency::create_task, tale attività non è cancellable.Inoltre, è necessario passare lo stesso token di annullamento al costruttore di tutte le attività annidate (ovvero attività create nel corpo di un'altra attività) annullamento contemporaneamente tutte le attività.

È possibile eseguire codice arbitrario quando un token di annullamento viene annullato.Ad esempio, se un utente sceglie un pulsante Annullainterfaccia utente per annullare l'operazione, è possibile disabilitare il pulsante finché non è stata avviata un'altra operazione.Di seguito 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) illustrata la differenza tra le continuazioni basate su valore e sull'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 valore eredita sempre il token di annullamento di attività precedente.

  • Una continuazione relativa alle attività non eredita mai il token di annullamento di attività precedente.L'unico modo per effettuare una continuazione basata su un'attività annullabile viene esplicitamente di passare un token di annullamento.

Questi comportamenti non sono interessati da un'attività presenta errori ovvero una che genera un'eccezione).In questo caso, una continuazione basata su valore viene annullata; una continuazione relativa alle attività non viene annullata.

Nota di avvisoAttenzione

Un'attività creata in un'altra attività (ovvero un'attività annidata) non eredita il token di annullamento di attività padre.Solo a una continuazione basata su valore eredita il token di annullamento della relativa attività precedente.

SuggerimentoSuggerimento

Utilizzare il metodo concurrency::cancellation_token::none quando si chiama un costruttore o eseguono che accetta un oggetto cancellation_token e non si desidera che l'operazione sia cancellable.

È anche possibile fornire un token di annullamento al costruttore di un oggetto structured_task_group o task_group.Un aspetto importante del fatto che i gruppi di attività figlio ereditano questo token di annullamento.Per un esempio in cui venga illustrato questo concetto utilizzando la funzione concurrency::run_with_cancellation_token eseguire per chiamare parallel_for, vedere annullare gli algoritmi paralleli avanti in questo documento.

[]Parte superiore

Dd984117.collapse_all(it-it,VS.110).gifToken di annullamento e composizione di attività

Le funzioni concurrency::when_any e concurrency::when_all consentono di 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 e when_all, annulla di tale funzione solo quando il token di annullamento viene annullato o quando uno della fine di attività di partecipante 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 a.L'attività restituita da when_all viene annullata quando uno di questi token vengono annullati e almeno una di partecipante che le attività non ancora avviata o che esegue.Un comportamento simile si verifica quando una delle attività genera un'eccezione – l'attività restituita da when_all immediatamente viene annullata con tale eccezione.

Il runtime consente il token di annullamento per l'attività restituita dalla funzione when_any quando l'attività completa.Se nessuna delle attività di partecipante completate in uno stato completato e uno o più delle attività genera un'eccezione, una delle attività che sono generate è scelta per completare when_any e il relativo token viene scelta del token per l'attività finale.Se più attività completata con lo stato completato, l'attività restituita dalla fine di un'attività when_any in uno stato completato.I test runtime per selezionare un'attività completa non annullare il cui token in caso di completamento in modo dall'attività restituita da when_any immediatamente non viene annullata anche se eseguire altre attività potrebbero completare in un secondo momento.

[]Parte superiore

Dd984117.collapse_all(it-it,VS.110).gifUtilizzo del metodo cancel per annullare un lavoro parallelo

I metodi concurrency::structured_task_group::cancel e concurrency::task_group::cancel impostare un gruppo di attività allo stato canceled.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 i metodi concurrency::structured_task_group::wait e concurrency::task_group::wait a restituire concurrency::canceled.

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 ha accesso all'oggetto padre gruppo di attività, chiamare la funzione concurrency::is_current_task_group_canceling per determinare se il gruppo di attività padre viene 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 metodi concurrency::structured_task_group::is_canceling e structured_task_group::cancel.Un'attività figlio può chiamare questi metodi per annullare il gruppo di attività padre o verificarne l'annullamento.

Nota di avvisoAttenzione

Sebbene sia possibile utilizzare un token di annullamento per annullare il lavoro eseguito da un gruppo di attività che funziona come figlio di un oggetto task, non è possibile utilizzare i metodi structured_task_group::cancel o task_group::cancel per annullare oggetti task eseguiti in un gruppo di attività.

[]Parte superiore

Dd984117.collapse_all(it-it,VS.110).gifUtilizzo delle eccezioni per annullare un lavoro parallelo

L'utilizzo di token di annullamento e il metodo cancel sono più efficienti della gestione delle eccezioni per annullare un albero di lavoro parallelo.I token di annullamento e il metodo cancel annullare 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.Ad esempio, un algoritmo di ricerca potrebbe uscire la relativa attività collegata 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.

Nota di avvisoAttenzione

È consigliabile utilizzare le eccezioni per annullare un lavoro parallelo solo se necessario.I token di annullamento e i metodi cancel gruppo di attività sono di errore più efficiente e meno soggetto.

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.

[]Parte superiore

Annullamento degli algoritmi paralleli

Algoritmi paralleli nella libreria PPL, ad esempio, parallel_for, 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 seguente viene utilizzata 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à, ereditano il token di annullamento di 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.

The task group status is: canceled.

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.

Caught 50

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.

[]Parte superiore

Casi i cui non è consigliabile utilizzare l'annullamento

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 Understand how Cancellation and Exception Handling Affect Object Destruction nelle procedure consigliate nell'argomento relativo alla libreria PPL.

[]Parte superiore

Argomenti correlati

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.

Gestione delle eccezioni nel runtime di concorrenza

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.

Parallelismo delle attività (runtime di concorrenza)

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.

Algoritmi paralleli

Vengono descritti gli algoritmi paralleli per svolgere simultaneamente il lavoro sulle raccolte di dati.

PPL (Parallel Patterns Library)

Vengono forniti i cenni preliminari sulla 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