Delen via


Annulering in de PPL

In dit document wordt de rol van annulering in de PPL (Parallel Patterns Library) uitgelegd, hoe u parallel werk annuleert en hoe u kunt bepalen wanneer parallel werk wordt geannuleerd.

Opmerking

De runtime maakt gebruik van uitzonderingsafhandeling om annulering te implementeren. Onderscheppen of verwerken deze uitzonderingen niet in uw code. Daarnaast raden we u aan om uitzonderingsveilige code te schrijven in de functieteksten voor uw taken. U kunt bijvoorbeeld het RAII-patroon ( Resource Acquisition Is Initialization ) gebruiken om ervoor te zorgen dat resources correct worden verwerkt wanneer er een uitzondering wordt gegenereerd in de hoofdtekst van een taak. Zie Walkthrough: Work from a User-Interface Thread (Werk verwijderen uit een User-Interface Thread) voor een volledig voorbeeld waarin het RAII-patroon wordt gebruikt om een resource op te schonen in een geannuleerde taak.

Belangrijkste punten

  • Annulering is coöperatief en omvat coördinatie tussen de code die annulering aanvraagt en de taak die op annulering reageert.

  • Gebruik indien mogelijk annuleringstokens om werk te annuleren. De concurrency::cancellation_token klasse definieert een annuleringstoken.

  • Wanneer u annulerings-tokens gebruikt, gebruikt u de methode concurrency::cancellation_token_source::cancel om de annulering te starten en de functie concurrency::cancel_current_task om te reageren op de annulering. Gebruik concurrency::cancellation_token::is_canceled om te controleren of een andere taak de annulering heeft aangevraagd.

  • Annulering vindt niet onmiddellijk plaats. Hoewel er geen nieuw werk wordt gestart als een taak of taakgroep wordt geannuleerd, moet actief werk controleren op en reageren op annulering.

  • Een waardegebaseerd vervolg neemt het annuleringstoken van de voorafgaande taak over. Een taakgebaseerd vervolg neemt nooit het token over van de voorafgaande taak.

  • Gebruik de concurrency::cancellation_token::none methode wanneer u een constructor of functie aanroept die een cancellation_token object gebruikt, maar u niet wilt dat de bewerking annuleerbaar is. Als u geen cancellation token doorgeeft aan de concurrency::task-constructor of de concurrency::create_task-functie, kan die taak niet worden geannuleerd.

In dit document

Parallelle werkstructuren

De PPL maakt gebruik van taken en taakgroepen om gedetailleerde taken en berekeningen te beheren. U kunt taakgroepen nesten om bomen van parallel werk te vormen. In de volgende afbeelding ziet u een parallelle werkstructuur. In deze illustratie vertegenwoordigen tg1 en tg2 taakgroepen; t1, t2, t3, t4 en t5 vertegenwoordigen het werk dat door de taakgroepen wordt uitgevoerd.

Een parallelle werkstructuur.

In het volgende voorbeeld ziet u de code die is vereist voor het maken van de structuur in de afbeelding. In dit voorbeeld zijn tg1 en tg2concurrency::structured_task_group objecten; t1, t2, t3, t4, en t5 zijn concurrency::task_handle objecten.

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

U kunt ook de gelijktijdigheidsklasse::task_group gebruiken om een vergelijkbare werkstructuur te maken. De concurrency::task-klasse ondersteunt ook het begrip van een boom van werk. Een task boomstructuur is echter een afhankelijkheidsstructuur. In een task boomstructuur wordt toekomstig werk voltooid nadat het huidige werk is voltooid. In een structuur van een taakgroep wordt het interne werk voltooid voordat het buitenste werk wordt uitgevoerd. Zie Taakparallellisme voor meer informatie over de verschillen tussen taken en taakgroepen.

[Boven]

Parallele taken afbreken

Er zijn meerdere manieren om parallel werk te annuleren. De voorkeursmethode is het gebruik van een annuleringstoken. Taakgroepen ondersteunen ook de concurrency::task_group::cancel-methode en de concurrency::structured_task_group::cancel-methode. De laatste manier is om een uitzondering in de hoofdtekst van een taakwerkfunctie te genereren. Ongeacht welke methode u kiest, begrijpt u dat annulering niet onmiddellijk plaatsvindt. Hoewel er geen nieuw werk wordt gestart als een taak of taakgroep wordt geannuleerd, moet actief werk controleren op en reageren op annulering.

Zie Walkthrough: Verbinding maken met behulp van taken en XML-HTTP-aanvragen, Hoe te: Annulering gebruiken om een parallelle lus te onderbreken en Hoe te: Uitzonderingsafhandeling gebruiken om een parallelle lus te onderbreken voor meer voorbeelden.

Een annuleringstoken gebruiken om parallel werk te annuleren

De task, task_groupen structured_task_group klassen ondersteunen annulering via het gebruik van annuleringstokens. De PPL definieert de concurrency::cancellation_token_source en concurrency::cancellation_token klassen voor dit doel. Wanneer u een annuleringstoken gebruikt om werk te annuleren, start de runtime geen nieuw werk dat is geabonneerd op dat token. Werk dat al actief is, kan de is_canceled lidfunctie gebruiken om het annuleringstoken te controleren en te stoppen wanneer dat mogelijk is.

Als u annulering wilt initiëren, roept u de gelijktijdigheid::cancellation_token_source::cancel-methode aan. U reageert op de annulering op deze manieren:

  • Gebruik de functie task voor objecten. cancel_current_task annuleert de huidige taak en alle op waarden gebaseerde continuaties. (Het annuleringstoken dat is gekoppeld aan de taak of de voortzettingen ervan, wordt niet geannuleerd.)

  • Voor taakgroepen en parallelle algoritmen gebruikt u de concurrency::is_current_task_group_canceling functie om annulering te detecteren en zo snel mogelijk terug te keren uit de taak wanneer deze functie true geeft. (Niet aanroepen cancel_current_task vanuit een taakgroep.)

In het volgende voorbeeld ziet u het eerste basispatroon voor het annuleren van taken. De hoofdtekst van de taak controleert af en toe op annulering binnen een lus.

// 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.
*/

De cancel_current_task functie genereert; daarom hoeft u niet expliciet terug te keren uit de huidige lus of functie.

Aanbeveling

U kunt ook de functie concurrency::interruption_point aanroepen in plaats van cancel_current_task.

Het is belangrijk om aan te roepen cancel_current_task wanneer u reageert op annulering, omdat de taak wordt overgezet naar de geannuleerde status. Als u vroeg terugkeert in plaats van aanroepen cancel_current_task, wordt de bewerking overgegaan naar de voltooide status en worden eventuele vervolgbewerkingen op basis van waarden uitgevoerd.

Waarschuwing

Gooi nooit task_canceled in uw code. Bel cancel_current_task in plaats daarvan.

Wanneer een taak eindigt in de geannuleerde status, gooit de methode concurrency::task::getconcurrency::task_canceled. (concurrency::task::wait retourneert task_status::geannuleerd en werpt geen uitzondering op.) Het volgende voorbeeld illustreert dit gedrag voor een taakgebaseerde voortzetting. Een vervolg op basis van een taak wordt altijd aangeroepen, zelfs wanneer de antecedent-taak wordt geannuleerd.

// 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.
*/

Omdat voortzettingen op basis van waarden het token overnemen van hun antecedent-taak, tenzij ze zijn gemaakt met een expliciet token, voeren de vervolgen onmiddellijk de geannuleerde status in, zelfs wanneer de antecedent-taak nog steeds wordt uitgevoerd. Daarom wordt elke uitzondering die wordt gegenereerd door de antecedent-taak na annulering niet doorgegeven aan de vervolgtaken. Annulering heft altijd de status van de voorafgaande taak op. Het volgende voorbeeld lijkt op de vorige, maar illustreert het gedrag voor een vervolg op basis van een waarde.

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.
*/

Waarschuwing

Als u geen annuleringstoken doorgeeft aan de task constructor of de gelijktijdigheidsfunctie::create_task , kan die taak niet worden geannuleerd. Bovendien moet u hetzelfde annuleringstoken doorgeven aan de constructor van geneste taken (dat wil zeggen taken die in het lichaam van een andere taak zijn gemaakt) om alle taken tegelijkertijd te annuleren.

Mogelijk wilt u willekeurige code uitvoeren wanneer een annuleringstoken wordt geannuleerd. Als uw gebruiker bijvoorbeeld een knop Annuleren kiest op de gebruikersinterface om de bewerking te annuleren, kunt u die knop uitschakelen totdat de gebruiker een andere bewerking start. In het volgende voorbeeld ziet u hoe u de concurrency::cancellation_token::register_callback methode gebruikt om een callback-functie te registreren die wordt uitgevoerd wanneer een cancellation token wordt geannuleerd.

// 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.
*/

In het document Taakparallelisme wordt het verschil uitgelegd tussen vervolgbewerkingen op basis van waarden en taken. Als u geen cancellation_token object aan een vervolgtaak opgeeft, neemt de voortzetting het annuleringstoken over van de antecedent-taak op de volgende manieren:

  • Een waardegebaseerd vervolg erft altijd het annulerings-token van de voorafgaande taak.

  • Een op-taak gebaseerde voortzetting neemt nooit het annulerings-token van de voorgaande taak over. De enige manier om een op taken gebaseerde voortzetting te annuleren is door expliciet een annuleringstoken door te geven.

Dit gedrag wordt niet beïnvloed door een foutieve taak (een taak die een uitzondering genereert). In dit geval wordt een vervolg op basis van een waarde geannuleerd; een vervolg op basis van een taak wordt niet geannuleerd.

Waarschuwing

Een taak die is gemaakt in een andere taak (met andere woorden, een geneste taak) neemt het annuleringstoken van de bovenliggende taak niet over. Alleen een waardegebasseerde voortzetting erft het annuleringstoken van de voorafgaande taak.

Aanbeveling

Gebruik de concurrency::cancellation_token::none methode wanneer u een constructor of functie aanroept die een cancellation_token object accepteert en u niet wilt dat de bewerking kan worden geannuleerd.

U kunt ook een annuleringstoken opgeven voor de constructor van een task_group of structured_task_group object. Een belangrijk aspect hiervan is dat onderliggende taakgroepen dit annuleringstoken overnemen. Zie Annuleren van parallelle algoritmen verderop in dit document voor een voorbeeld waarin dit concept wordt gedemonstreerd door gebruik te maken van de functie parallel_for om uit te voeren en aan te roepen.

[Boven]

Annuleringstokens en taaksamenstelling

De functies gelijktijdigheid::when_all en gelijktijdigheid::when_any kunnen u helpen bij het opstellen van meerdere taken om algemene patronen te implementeren. In deze sectie wordt beschreven hoe deze functies werken met annuleringstokens.

Wanneer u een annuleringstoken opgeeft aan de when_all en when_any functie, wordt deze functie alleen geannuleerd wanneer dat annuleringstoken wordt geannuleerd of wanneer een van de deelnemerstaken eindigt in een geannuleerde status of een uitzondering genereert.

De when_all functie neemt het annuleringstoken over van elke taak die de algehele bewerking opstelt wanneer u er geen annuleringstoken aan opgeeft. De taak die wordt geretourneerd uit when_all wordt geannuleerd als een van deze tokens wordt geannuleerd en ten minste één van de deelneemtaken nog niet is gestart of wordt uitgevoerd. Er treedt een vergelijkbaar gedrag op wanneer een van de taken een uitzondering genereert: de taak die wordt geretourneerd door when_all wordt onmiddellijk geannuleerd door die uitzondering.

Wanneer de taak voltooid is, kiest de runtime het annuleringstoken voor de taak die door de functie when_any wordt geretourneerd. Als geen van de deelnemerstaken in een voltooide status eindigt en een of meer van de taken een uitzondering genereert, wordt een van de taken die een uitzondering heeft gegooid gekozen om het when_any te voltooien en wordt diens token gekozen als het token voor de uiteindelijke taak. Als meer dan één taak eindigt in de voltooide status, dan is de taak die wordt geretourneerd van when_any in een voltooide status. De runtime probeert een voltooide taak te kiezen waarvan het token niet wordt geannuleerd op het moment van voltooiing, zodat de geretourneerde taak when_any niet onmiddellijk wordt geannuleerd, ook al kunnen andere uitvoertaken op een later moment worden voltooid.

[Boven]

De methode annuleren gebruiken om parallel werk te annuleren

De concurrency::task_group::cancel en concurrency::structured_task_group::cancel-methoden zetten een task group in de geannuleerde status. Nadat u de aanroep cancelhebt uitgevoerd, start de taakgroep geen toekomstige taken. De cancel methoden kunnen worden aangeroepen door meerdere onderliggende taken. Een geannuleerde taak zorgt ervoor dat de methoden concurrency::task_group::wait en concurrency::structured_task_group::waitconcurrency::canceled retourneren.

Als een taakgroep is geannuleerd, kunnen aanroepen van elke onderliggende taak binnen de runtime een onderbrekingspunt triggeren, waardoor de runtime een interne uitzondering genereert en opvangt om actieve taken te annuleren. De Gelijktijdigheidsruntime definieert geen specifieke onderbrekingspunten; ze kunnen optreden in elke aanroep van de runtime. De runtime moet de uitzonderingen verwerken die deze genereert om annulering uit te voeren. Behandel daarom geen onbekende uitzonderingen binnen een taak.

Als een onderliggende taak een tijdrovende bewerking uitvoert en de runtime niet aanroept, moet deze periodiek controleren op annulering en tijdig afsluiten. In het volgende voorbeeld ziet u een manier om te bepalen wanneer het werk wordt geannuleerd. De t4 taak annuleert de bovenliggende taakgroep wanneer er een fout optreedt. Taak t5 roept af en toe de structured_task_group::is_canceling methode aan om te controleren op annulering. Als de bovenliggende taakgroep is geannuleerd, wordt een bericht afgedrukt en wordt de taak t5 afgesloten.

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 
      // cancellation.
      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 dit voorbeeld wordt gecontroleerd op annulering bij elke100e iteratie van de taaklus. De frequentie waarmee u op annulering controleert, is afhankelijk van de hoeveelheid werk die uw taak uitvoert en hoe snel u taken nodig hebt om te reageren op annulering.

Als u geen toegang hebt tot het bovenliggende taakgroepobject, gebruikt u de functie concurrency::is_current_task_group_canceling om te bepalen of de bovenliggende taakgroep is geannuleerd.

De cancel methode is alleen van invloed op subtaken. Als u bijvoorbeeld de taakgroep tg1 annuleert in de afbeelding van de parallelle werkstructuur, worden alle taken in de structuur (t1, t2t3, , t4en t5) beïnvloed. Als u de geneste taakgroep annuleert, tg2, worden alleen de taken t4 en t5 beïnvloed.

Wanneer u de cancel methode aanroept, worden alle onderliggende taakgroepen ook geannuleerd. Annulering heeft echter geen invloed op de bovenliggende niveaus van de taakgroep in een parallelle werkstructuur. In de volgende voorbeelden ziet u dit door voort te bouwen op de parallelle afbeelding van de werkstructuur.

Met de eerste van deze voorbeelden wordt een werkfunctie voor de taak t4gemaakt. Dit is een onderliggend element van de taakgroep tg2. Met de werkfunctie wordt de functie work in een lus aangeroepen. Als een aanroep naar work mislukt, annuleert de taak de bovenliggende taakgroep. Dit zorgt ervoor dat de taakgroep tg2 de geannuleerde status invoert, maar de taakgroep tg1wordt niet geannuleerd.

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

Dit tweede voorbeeld lijkt op de eerste, behalve dat de taak de taakgroep tg1annuleert. Dit is van invloed op alle taken in de structuur (t1, t2, t3, t4, en 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;
      }
   }   
});

De structured_task_group klasse is niet draadveilig. Daarom produceert een onderliggende taak die een methode aanroept van het bovenliggende structured_task_group object niet-opgegeven gedrag. De uitzonderingen op deze regel zijn de methoden structured_task_group::cancel en concurrency::structured_task_group::is_canceling. Een kindtaak kan deze methoden aanroepen om de oudertaakgroep te annuleren en te controleren op annulering.

Waarschuwing

Hoewel u een annuleringstoken kunt gebruiken om werk te annuleren dat wordt uitgevoerd door een taakgroep die wordt uitgevoerd als onderliggend element van een task object, kunt u de task_group::cancel of structured_task_group::cancel methoden niet gebruiken om objecten te annuleren task die in een taakgroep worden uitgevoerd.

[Boven]

Uitzonderingen gebruiken om parallel werk te annuleren

Het gebruik van annuleringstokens en de cancel-methode is efficiënter dan het verwerken van uitzonderingen bij het annuleren van een parallelle werkstructuur. Annuleringstokens en de cancel methode annuleren een taak en alle onderliggende taken van boven naar beneden. Omgekeerd werkt de afhandeling van uitzonderingen van onder naar boven en moet elke onderliggende taakgroep onafhankelijk worden geannuleerd terwijl de uitzondering naar boven wordt doorgegeven. In het onderwerp Uitzonderingsafhandeling wordt uitgelegd hoe de Gelijktijdigheidsruntime uitzonderingen gebruikt om fouten te communiceren. Niet alle uitzonderingen geven echter een fout aan. Een zoekalgoritmen kunnen bijvoorbeeld de bijbehorende taak annuleren wanneer het resultaat wordt gevonden. Zoals eerder vermeld, is de verwerking van uitzonderingen echter minder efficiënt dan het gebruik van de cancel methode om parallel werk te annuleren.

Waarschuwing

We raden u aan om uitzonderingen te gebruiken om parallelle werkzaamheden alleen te annuleren wanneer dat nodig is. Annuleringstokens en de methoden van de taakgroep cancel zijn efficiënter en minder gevoelig voor fouten.

Wanneer u een uitzondering genereert in de hoofdtekst van een werkfunctie die u doorgeeft aan een taakgroep, slaat de runtime deze uitzondering op en wordt de uitzondering opgeslagen in de context die wacht totdat de taakgroep is voltooid. Net als bij de cancel methode verwijdert de runtime alle taken die nog niet zijn gestart en accepteert deze geen nieuwe taken.

Dit derde voorbeeld lijkt op de tweede, behalve dat de taak t4 een uitzondering genereert om de taakgroep tg2te annuleren. In dit voorbeeld wordt een try-catch blok gebruikt om te controleren op annulering wanneer de taakgroep tg2 wacht tot de onderliggende taken zijn voltooid. Net als in het eerste voorbeeld krijgt de taakgroep tg2 de status geannuleerd, maar wordt de taakgroep tg1 niet geannuleerd.

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;
}

In dit vierde voorbeeld wordt uitzonderingsafhandeling gebruikt om de hele werkstructuur te annuleren. In het voorbeeld wordt de uitzondering onderschept wanneer taakgroep tg1 wacht tot de onderliggende taken zijn voltooid in plaats van wanneer taakgroep tg2 op de onderliggende taken wacht. Net als in het tweede voorbeeld zorgt dit ervoor dat beide taakgroepen in de structuur, tg1 en tg2, de geannuleerde status ingaan.

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

Omdat de task_group::wait en structured_task_group::wait methoden worden gegenereerd wanneer een onderliggende taak een uitzondering genereert, ontvangt u er geen retourwaarde van.

[Boven]

Parallelle algoritmen annuleren

Parallelle algoritmen in de PPL bouwen bijvoorbeeld parallel_forop taakgroepen. Daarom kunt u veel van dezelfde technieken gebruiken om een parallel algoritme te annuleren.

In de volgende voorbeelden ziet u verschillende manieren om een parallel algoritme te annuleren.

In het volgende voorbeeld wordt de run_with_cancellation_token functie gebruikt om het parallel_for algoritme aan te roepen. De run_with_cancellation_token functie gebruikt een annuleringstoken als argument en roept de opgegeven werkfunctie synchroon aan. Omdat parallelle algoritmen zijn gebaseerd op taken, nemen ze het annuleringstoken van de bovenliggende taak over. parallel_for Kan daarom reageren op annulering.

// 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
*/

In het volgende voorbeeld wordt de concurrency::structured_task_group::run_and_wait-methode gebruikt om het parallel_for-algoritme aan te roepen. De structured_task_group::run_and_wait methode wacht tot de opgegeven taak is voltooid. Met structured_task_group het object kan de werkfunctie de taak annuleren.

// To enable cancellation, 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;
}

In dit voorbeeld wordt de volgende uitvoer geproduceerd.

The task group status is: canceled.

In het volgende voorbeeld wordt uitzonderingsafhandeling gebruikt om een parallel_for lus te annuleren. De uitvoeringstijd draagt de exceptie over naar de aanroepcontext.

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;
}

In dit voorbeeld wordt de volgende uitvoer geproduceerd.

Caught 50

In het volgende voorbeeld wordt een Booleaanse vlag gebruikt om de annulering in een parallel_for lus te coördineren. Elke taak wordt uitgevoerd omdat in dit voorbeeld de cancel methode of uitzonderingsafhandeling niet wordt gebruikt om de algehele set taken te annuleren. Daarom kan deze techniek meer rekenoverhead hebben dan een annuleringsmechanisme.

// Create a Boolean flag to coordinate cancellation.
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.
   }
});

Elke annuleringsmethode heeft voordelen ten opzichte van de andere. Kies de methode die past bij uw specifieke behoeften.

[Boven]

Wanneer u annulering niet wilt gebruiken

Het gebruik van annulering is geschikt wanneer elk lid van een groep gerelateerde taken tijdig kan afsluiten. Er zijn echter enkele scenario's waarin annulering mogelijk niet geschikt is voor uw toepassing. Omdat taakannulering bijvoorbeeld coöperatief is, wordt de algehele set taken niet geannuleerd als een afzonderlijke taak wordt geblokkeerd. Als een taak bijvoorbeeld nog niet is gestart, maar een andere actieve taak deblokkert, wordt deze niet gestart als de taakgroep wordt geannuleerd. Dit kan ertoe leiden dat er een impasse optreedt in uw toepassing. Een tweede voorbeeld van waar het gebruik van annulering mogelijk niet geschikt is, is wanneer een taak wordt geannuleerd, maar de onderliggende taak een belangrijke bewerking uitvoert, zoals het vrijmaken van een resource. Omdat de algehele set taken wordt geannuleerd wanneer de bovenliggende taak wordt geannuleerd, wordt die bewerking niet uitgevoerd. Voor een voorbeeld dat dit punt illustreert, zie de sectie 'Begrijpen hoe annulering en uitzonderingsafhandeling van invloed zijn op objectvernietiging' in het onderwerp Beste praktijken in de Parallelle patronenbibliotheek.

[Boven]

Titel Beschrijving
Procedure: Annulering gebruiken om een parallelle lus te verbreken Laat zien hoe u annulering gebruikt om een parallel zoekalgoritmen te implementeren.
Procedure: Uitzonderingsafhandeling gebruiken om te breken van een parallelle lus Laat zien hoe u de task_group klasse gebruikt om een zoekalgoritme te schrijven voor een basisstructuur.
afhandeling van uitzonderingen Beschrijft hoe de runtime uitzonderingen verwerkt die worden gegenereerd door taakgroepen, lichtgewicht taken en asynchrone agents en hoe u kunt reageren op uitzonderingen in uw toepassingen.
Parallelle uitvoering van taken Hierin wordt beschreven hoe taken betrekking hebben op taakgroepen en hoe u ongestructureerde en gestructureerde taken in uw toepassingen kunt gebruiken.
Parallelle algoritmen Beschrijft de parallelle algoritmen, die gelijktijdig werk uitvoeren op verzamelingen gegevens
Bibliotheek met parallelle patronen (PPL) Biedt een overzicht van de bibliotheek met parallelle patronen.

Referentie

taakklasse (Gelijktijdigheidsruntime)

Cancellation_Token_Source-Klasse

cancellation_token-klasse

task_group-klasse

structured_task_group-klasse

parallel_for, functie