Anulowanie w PPL

W tym dokumencie wyjaśniono rolę anulowania w bibliotece równoległych wzorców (PPL), jak anulować pracę równoległą oraz jak określić, kiedy równoległa praca jest anulowana.

Uwaga

Środowisko uruchomieniowe używa obsługi wyjątków w celu zaimplementowania anulowania. Nie przechwytuj ani nie obsłuż tych wyjątków w kodzie. Ponadto zalecamy pisanie kodu bezpiecznego pod kątem wyjątków w ciałach funkcji dla zadań podrzędnych. Na przykład można użyć wzorca Pozyskiwanie zasobów jest inicjowania (RAII), aby upewnić się, że zasoby są prawidłowo obsługiwane, gdy wyjątek jest zgłaszany w treści zadania. Pełny przykład, który używa wzorca RAII do czyszczenia zasobu w zadaniu, które można anulować, zobacz Przewodnik: usuwanie pracy z wątku interfejsu użytkownika.

Kwestie kluczowe

W tym dokumencie

Równoległe drzewa robocze

PPL używa zadań i grup zadań do zarządzania precyzyjnymi zadaniami i obliczeniami. Grupy zadań można zagnieżdżać w celu utworzenia drzew równoległej pracy. Na poniższej ilustracji przedstawiono równoległe drzewo robocze. Na tej ilustracji tg1 i tg2 reprezentują grupy zadań, t1, t2, t3, t4i t5 reprezentują pracę wykonywaną przez grupy zadań.

A parallel work tree.

Poniższy przykład przedstawia kod wymagany do utworzenia drzewa na ilustracji. W tym przykładzie tg1 obiekty tg2współbieżności::structured_task_group; t1, t2t3, , t4i t5obiektami współbieżności::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();   
}

Możesz również użyć klasy concurrency::task_group , aby utworzyć podobne drzewo robocze. Klasa concurrency::task obsługuje również pojęcie drzewa pracy. Jednak drzewo jest drzewem task zależności. W drzewie przyszłe prace zakończą się po bieżącej task pracy. W drzewie grupy zadań wewnętrzna praca kończy się przed pracą zewnętrzną. Aby uzyskać więcej informacji na temat różnic między zadaniami i grupami zadań, zobacz Równoległość zadań.

[Top]

Anulowanie zadań równoległych

Istnieje wiele sposobów anulowania równoległej pracy. Preferowanym sposobem jest użycie tokenu anulowania. Grupy zadań obsługują również metodę concurrency::task_group::cancel oraz metodę concurrency::structured_task_group::cancel . Ostatnim sposobem jest zgłoszenie wyjątku w treści funkcji pracy zadania. Niezależnie od wybranej metody należy zrozumieć, że anulowanie nie nastąpi natychmiast. Mimo że nowa praca nie jest uruchamiana, jeśli zadanie lub grupa zadań jest anulowane, aktywna praca musi sprawdzać i odpowiadać na anulowanie.

Aby uzyskać więcej przykładów, które anulują zadania równoległe, zobacz Przewodnik: Połączenie przy użyciu zadań i żądań HTTP XML, Instrukcje: używanie anulowania do przerwania z pętli równoległej i Instrukcje: Używanie obsługi wyjątków do przerwania z pętli równoległej.

Używanie tokenu anulowania do anulowania równoległej pracy

Klasy task, task_groupi structured_task_group obsługują anulowanie za pomocą tokenów anulowania. W tym celu PPL definiuje klasy concurrency::cancellation_token_source i concurrency::cancellation_token . Jeśli używasz tokenu anulowania do anulowania pracy, środowisko uruchomieniowe nie uruchamia nowej pracy, która subskrybuje ten token. Praca, która jest już aktywna, może używać funkcji składowej is_canceled do monitorowania tokenu anulowania i zatrzymywania, kiedy to możliwe.

Aby zainicjować anulowanie, wywołaj metodę concurrency::cancellation_token_source::cancel . Odpowiadasz na anulowanie na następujące sposoby:

  • W przypadku task obiektów użyj funkcji concurrency::cancel_current_task . cancel_current_task Anuluje bieżące zadanie i dowolne z jego kontynuacji opartych na wartości. (Nie anuluje tokenu anulowania skojarzonego z zadaniem lub jego kontynuacjami).

  • W przypadku grup zadań i algorytmów równoległych użyj funkcji concurrency::is_current_task_group_canceling , aby wykryć anulowanie i zwrócić je tak szybko, jak to możliwe z treści zadania, gdy ta funkcja zwróci truewartość . (Nie należy wywoływać cancel_current_task z grupy zadań).

W poniższym przykładzie przedstawiono pierwszy podstawowy wzorzec anulowania zadania. Treść zadania od czasu do czasu sprawdza anulowanie wewnątrz pętli.

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

Funkcja cancel_current_task zgłasza, dlatego nie trzeba jawnie zwracać z bieżącej pętli ani funkcji.

Napiwek

Alternatywnie można wywołać funkcję concurrency::interruption_point zamiast cancel_current_task.

Ważne jest, aby wywołać cancel_current_task wywołanie podczas odpowiadania na anulowanie, ponieważ przenosi zadanie do stanu anulowanego. Jeśli zwracasz wcześniej zamiast wywoływać cancel_current_taskmetodę , operacja przechodzi do stanu ukończonego, a wszystkie kontynuacje oparte na wartości są uruchamiane.

Uwaga

Nigdy nie zgłaszaj task_canceled kodu. Wywołaj cancel_current_task zamiast tego.

Gdy zadanie kończy się w stanie anulowania, współbieżność::task::get metoda zgłasza współbieżność::task_canceled. (Z drugiej strony współbieżność ::task::wait zwraca wartość task_status::canceled i nie zgłasza). W poniższym przykładzie pokazano to zachowanie dla kontynuacji opartej na zadaniach. Kontynuacja oparta na zadaniach jest zawsze wywoływana, nawet gdy zadanie przeddentowe zostanie anulowane.

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

Ponieważ kontynuacje oparte na wartości dziedziczą token zadania przeddentowego, chyba że zostały utworzone za pomocą jawnego tokenu, kontynuacje natychmiast wchodzą w stan anulowany, nawet jeśli zadanie antecedent jest nadal wykonywane. W związku z tym wszelkie wyjątki zgłaszane przez zadanie przeddent po anulowaniu nie są propagowane do zadań kontynuacji. Anulowanie zawsze zastępuje stan zadania antecedent. Poniższy przykład przypomina poprzedni, ale ilustruje zachowanie kontynuacji opartej na wartościach.

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

Uwaga

Jeśli nie przekażesz tokenu anulowania do task konstruktora lub funkcji współbieżności::create_task , nie można anulować tego zadania. Ponadto należy przekazać ten sam token anulowania do konstruktora wszystkich zagnieżdżonych zadań (czyli zadań utworzonych w treści innego zadania), aby anulować wszystkie zadania jednocześnie.

Możesz chcieć uruchomić dowolny kod po anulowaniu tokenu anulowania. Jeśli na przykład użytkownik wybierze przycisk Anuluj w interfejsie użytkownika, aby anulować operację, możesz wyłączyć ten przycisk, dopóki użytkownik nie uruchomi innej operacji. W poniższym przykładzie pokazano, jak użyć metody concurrency::cancellation_token::register_callback w celu zarejestrowania funkcji wywołania zwrotnego uruchamianej po anulowaniu tokenu anulowania.

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

Dokument Równoległość zadań wyjaśnia różnicę między kontynuacjami opartymi na wartościach i zadaniami. Jeśli nie podasz cancellation_token obiektu do zadania kontynuacji, kontynuacja dziedziczy token anulowania z zadania antecedent w następujący sposób:

  • Kontynuacja oparta na wartości zawsze dziedziczy token anulowania zadania antecedent.

  • Kontynuacja oparta na zadaniach nigdy nie dziedziczy tokenu anulowania zadania antecedent. Jedynym sposobem na anulowanie kontynuacji opartej na zadaniach jest jawne przekazanie tokenu anulowania.

Te zachowania nie mają wpływu na uszkodzone zadanie (czyli takie, które zgłasza wyjątek). W takim przypadku kontynuacja oparta na wartości została anulowana; kontynuacja oparta na zadaniu nie jest anulowana.

Uwaga

Zadanie utworzone w innym zadaniu (innymi słowy zadanie zagnieżdżone) nie dziedziczy tokenu anulowania zadania nadrzędnego. Tylko kontynuacja oparta na wartości dziedziczy token anulowania zadania wcześniejszego.

Napiwek

Użyj metody concurrency::cancellation_token::none podczas wywoływania konstruktora lub funkcji, która przyjmuje cancellation_token obiekt i nie chcesz, aby operacja mogła zostać anulowana.

Możesz również podać token anulowania konstruktorowi task_group obiektu lub structured_task_group . Ważnym aspektem jest to, że podrzędne grupy zadań dziedziczą ten token anulowania. Przykład, który demonstruje tę koncepcję przy użyciu funkcji concurrency::run_with_cancellation_token do uruchamiania w celu wywołania parallel_formetody , zobacz Anulowanie algorytmów równoległych w dalszej części tego dokumentu.

[Top]

Anulowanie tokenów i kompozycji zadania

Funkcje współbieżności::when_all i współbieżności::when_any mogą ułatwić tworzenie wielu zadań w celu zaimplementowania typowych wzorców. W tej sekcji opisano, jak te funkcje działają z tokenami anulowania.

Po podaniu tokenu anulowania do when_all funkcji i when_any funkcja ta anuluje tylko wtedy, gdy token anulowania zostanie anulowany lub gdy jedno z zadań uczestnika kończy się w stanie anulowanym lub zgłasza wyjątek.

Funkcja when_all dziedziczy token anulowania z każdego zadania, które komponuje ogólną operację, gdy nie podasz tokenu anulowania. Zadanie zwracane z when_all programu jest anulowane, gdy którykolwiek z tych tokenów zostanie anulowany, a co najmniej jedno z zadań uczestnika nie zostało jeszcze uruchomione lub jest uruchomione. Podobne zachowanie występuje, gdy jedno z zadań zgłasza wyjątek — zadanie, z when_all którego jest zwracane, jest natychmiast anulowane z tym wyjątkiem.

Środowisko uruchomieniowe wybiera token anulowania zadania zwracanego z when_any funkcji po zakończeniu tego zadania. Jeśli żadne z zadań uczestnika nie zakończy się w stanie ukończonym i co najmniej jedno zadanie zgłasza wyjątek, jedno z zadań, które zostały rzucone, jest wybierane do ukończenia when_any zadania, a jego token jest wybierany jako token zadania końcowego. Jeśli więcej niż jedno zadanie zakończy się w stanie ukończonym, zadanie zwracane z when_any zadania kończy się w stanie ukończenia. Środowisko uruchomieniowe próbuje wybrać ukończone zadanie, którego token nie jest anulowany w momencie ukończenia, aby zadanie zwrócone z when_any programu nie zostało natychmiast anulowane, mimo że inne zadania wykonujące mogą zostać wykonane w późniejszym momencie.

[Top]

Używanie metody cancel do anulowania równoległej pracy

Współbieżność ::task_group::cancel i współbieżność::structured_task_group::cancel metody ustawiają grupę zadań na anulowany stan. Po wywołaniu cancelpolecenia grupa zadań nie uruchamia przyszłych zadań. Metody cancel mogą być wywoływane przez wiele zadań podrzędnych. Anulowane zadanie powoduje zwrócenie metod współbieżności::task_group::wait i concurrency::structured_task_group::wait w celu zwrócenia współbieżności::canceled.

Jeśli grupa zadań zostanie anulowana, wywołania z każdego zadania podrzędnego do środowiska uruchomieniowego mogą wyzwolić punkt przerwania, co powoduje, że środowisko uruchomieniowe zgłasza i przechwytuje wewnętrzny typ wyjątku w celu anulowania aktywnych zadań. Środowisko uruchomieniowe współbieżności nie definiuje określonych punktów przerwania; mogą wystąpić w dowolnym wywołaniu środowiska uruchomieniowego. Środowisko uruchomieniowe musi obsługiwać zgłoszone wyjątki w celu przeprowadzenia anulowania. W związku z tym nie obsługują nieznanych wyjątków w treści zadania.

Jeśli zadanie podrzędne wykonuje czasochłonną operację i nie wywołuje środowiska uruchomieniowego, musi okresowo sprawdzać anulowanie i zakończyć w odpowiednim czasie. Poniższy przykład przedstawia jeden ze sposobów określania, kiedy praca jest anulowana. Zadanie t4 anuluje nadrzędną grupę zadań, gdy wystąpi błąd. Zadanie t5 od czasu do czasu wywołuje metodę structured_task_group::is_canceling w celu sprawdzenia anulowania. Jeśli nadrzędna grupa zadań zostanie anulowana, zadanie t5 wyświetli komunikat i zakończy działanie.

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

Ten przykład sprawdza anulowanie co 100 iteracji pętli zadań. Częstotliwość sprawdzania anulowania zależy od ilości pracy wykonywanej przez zadanie i szybkiego reagowania na anulowanie zadań.

Jeśli nie masz dostępu do obiektu nadrzędnej grupy zadań, wywołaj funkcję concurrency::is_current_task_group_canceling , aby określić, czy nadrzędna grupa zadań została anulowana.

Metoda cancel ma wpływ tylko na zadania podrzędne. Jeśli na przykład anulujesz grupę tg1 zadań na ilustracji równoległego drzewa roboczego, wszystkie zadania w drzewie (t1, t2, t3t4, i t5) będą miały wpływ. W przypadku anulowania zagnieżdżonej grupy tg2zadań będą miały wpływ tylko zadania t4 i t5 .

Po wywołaniu cancel metody wszystkie podrzędne grupy zadań również zostaną anulowane. Jednak anulowanie nie ma wpływu na żadnych elementów nadrzędnych grupy zadań w równoległym drzewie roboczym. W poniższych przykładach pokazano to, tworząc ilustrację równoległego drzewa roboczego.

Pierwszy z tych przykładów tworzy funkcję pracy dla zadania t4, która jest elementem podrzędnym grupy tg2zadań . Funkcja robocza wywołuje funkcję work w pętli. Jeśli jakiekolwiek wywołanie nie powiedzie się work , zadanie anuluje nadrzędną grupę zadań. Powoduje to, że grupa tg2 zadań wprowadza stan anulowany, ale nie anuluje grupy tg1zadań .

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

Ten drugi przykład przypomina pierwszy, z tą różnicą, że zadanie anuluje grupę tg1zadań . Ma to wpływ na wszystkie zadania w drzewie (t1, t2, t3, t4i 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;
      }
   }   
});

Klasa structured_task_group nie jest bezpieczna wątkowo. W związku z tym podrzędne zadanie, które wywołuje metodę obiektu nadrzędnego structured_task_group , generuje nieokreślone zachowanie. Wyjątki od tej reguły to structured_task_group::cancel metody i concurrency::structured_task_group::is_canceling . Podrzędne zadanie może wywołać te metody, aby anulować nadrzędną grupę zadań i sprawdzić anulowanie.

Uwaga

Chociaż można użyć tokenu anulowania do anulowania pracy wykonywanej przez grupę zadań, która jest uruchamiana jako element podrzędny task obiektu, nie można użyć task_group::cancel metod lub structured_task_group::cancel do anulowania task obiektów uruchamianych w grupie zadań.

[Top]

Używanie wyjątków w celu anulowania równoległej pracy

Użycie tokenów anulowania i cancel metody jest bardziej wydajne niż obsługa wyjątków podczas anulowania równoległego drzewa roboczego. Tokeny anulowania i cancel metoda anulują zadanie i wszystkie zadania podrzędne w sposób od góry do dołu. Z drugiej strony obsługa wyjątków działa w sposób dolny i musi anulować niezależnie każdą podrzędną grupę zadań, ponieważ wyjątek jest propagowany w górę. W temacie Obsługa wyjątków wyjaśniono, w jaki sposób środowisko uruchomieniowe współbieżności używa wyjątków do komunikowania się z błędami. Jednak nie wszystkie wyjątki wskazują błąd. Na przykład algorytm wyszukiwania może anulować skojarzone zadanie po znalezieniu wyniku. Jednak jak wspomniano wcześniej, obsługa wyjątków jest mniej wydajna cancel niż użycie metody w celu anulowania równoległej pracy.

Uwaga

Zalecamy używanie wyjątków w celu anulowania równoległej pracy tylko wtedy, gdy jest to konieczne. Tokeny anulowania i metody grupy cancel zadań są bardziej wydajne i mniej podatne na błędy.

Po wystąpieniu wyjątku w treści funkcji pracy przekazanej do grupy zadań środowisko uruchomieniowe przechowuje ten wyjątek i marshaluje wyjątek do kontekstu, który czeka na zakończenie grupy zadań. Podobnie jak w przypadku cancel metody, środowisko uruchomieniowe odrzuca wszystkie zadania, które nie zostały jeszcze uruchomione i nie akceptuje nowych zadań.

Ten trzeci przykład przypomina drugi, z tą różnicą, że zadanie t4 zgłasza wyjątek, aby anulować grupę tg2zadań . W tym przykładzie użyto try-catch bloku do sprawdzenia anulowania, gdy grupa tg2 zadań czeka na zakończenie zadań podrzędnych. Podobnie jak w pierwszym przykładzie, powoduje to, że grupa tg2 zadań wprowadza stan anulowany, ale nie anuluje grupy tg1zadań .

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

W tym czwartym przykładzie użyto obsługi wyjątków w celu anulowania całego drzewa roboczego. Przykład przechwytuje wyjątek, gdy grupa tg1 zadań czeka na zakończenie zadań podrzędnych zamiast wtedy, gdy grupa tg2 zadań czeka na zadania podrzędne. Podobnie jak w drugim przykładzie, powoduje to, że obie grupy zadań w drzewie tg1 i tg2, mają wprowadzić stan anulowany.

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

task_group::wait Ponieważ metody i structured_task_group::wait zgłaszają wyjątek, gdy zadanie podrzędne zgłasza wyjątek, nie otrzymujesz z nich wartości zwracanej.

[Top]

Anulowanie algorytmów równoległych

Algorytmy równoległe w PPL, na przykład , parallel_forbazują na grupach zadań. W związku z tym można użyć wielu tych samych technik, aby anulować algorytm równoległy.

W poniższych przykładach przedstawiono kilka sposobów anulowania algorytmu równoległego.

W poniższym przykładzie użyto run_with_cancellation_token funkcji do wywołania algorytmu parallel_for . Funkcja run_with_cancellation_token przyjmuje token anulowania jako argument i synchronicznie wywołuje podaną funkcję pracy. Ponieważ algorytmy równoległe są oparte na zadaniach, dziedziczą token anulowania zadania nadrzędnego. W związku z parallel_for tym może odpowiedzieć na anulowanie.

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

W poniższym przykładzie użyto metody concurrency::structured_task_group::run_and_wait w celu wywołania algorytmu parallel_for . Metoda structured_task_group::run_and_wait czeka na zakończenie podanego zadania. Obiekt structured_task_group umożliwia funkcji roboczej anulowanie zadania.

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

W tym przykładzie są generowane następujące dane wyjściowe.

The task group status is: canceled.

W poniższym przykładzie użyto obsługi wyjątków w celu anulowania parallel_for pętli. Środowisko uruchomieniowe marshaluje wyjątek do kontekstu wywołującego.

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

W tym przykładzie są generowane następujące dane wyjściowe.

Caught 50

W poniższym przykładzie użyto flagi logicznej do koordynowania anulowania w parallel_for pętli. Każde zadanie jest uruchamiane, ponieważ w tym przykładzie nie jest używana cancel metoda ani obsługa wyjątków w celu anulowania ogólnego zestawu zadań. W związku z tym ta technika może mieć większe obciążenie obliczeniowe niż mechanizm anulowania.

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

Każda metoda anulowania ma przewagę nad innymi. Wybierz metodę, która odpowiada konkretnym potrzebom.

[Top]

Kiedy nie należy używać anulowania

Użycie anulowania jest odpowiednie, gdy każdy członek grupy powiązanych zadań może zakończyć się w odpowiednim czasie. Istnieją jednak pewne scenariusze, w których anulowanie może nie być odpowiednie dla aplikacji. Na przykład ze względu na to, że anulowanie zadania jest współpracy, ogólny zestaw zadań nie zostanie anulowany, jeśli żadne pojedyncze zadanie zostanie zablokowane. Jeśli na przykład jedno zadanie nie zostało jeszcze uruchomione, ale odblokuje inne aktywne zadanie, nie zostanie uruchomione, jeśli grupa zadań zostanie anulowana. Może to spowodować zakleszczenie w aplikacji. Drugim przykładem, w którym użycie anulowania może nie być odpowiednie, jest anulowanie zadania podrzędnego, ale jego podrzędne zadanie wykonuje ważną operację, taką jak zwalnianie zasobu. Ponieważ ogólny zestaw zadań jest anulowany po anulowaniu zadania nadrzędnego, ta operacja nie zostanie wykonana. Aby zapoznać się z przykładem tego punktu, zobacz sekcję Understand how Cancellation and Exception Handling Affect Object Destruction (Opis sposobu anulowania i obsługi wyjątków) w temacie Best Practices in the Parallel Patterns Library (Najlepsze rozwiązania w bibliotece wzorców równoległych).

[Top]

Nazwa opis
Instrukcje: używanie anulowania, aby przerwać pętlę równoległą Pokazuje, jak używać anulowania do implementowania algorytmu wyszukiwania równoległego.
Instrukcje: używanie obsługi wyjątków, aby przerwać pętlę równoległą Pokazuje, jak używać klasy do pisania algorytmu task_group wyszukiwania dla podstawowej struktury drzewa.
Obsługa wyjątków Opisuje sposób obsługi wyjątków zgłaszanych przez grupy zadań, uproszczone zadania i agentów asynchronicznych oraz reagowanie na wyjątki w aplikacjach.
Równoległość zadań Opisuje sposób, w jaki zadania odnoszą się do grup zadań oraz jak można używać zadań bez struktury i zadań ustrukturyzowanych w aplikacjach.
Algorytmy równoległe Opisuje algorytmy równoległe, które współbieżnie wykonują pracę nad kolekcjami danych
Biblioteka równoległych wzorców (PLL) Zawiera omówienie biblioteki wzorców równoległych.

Odwołanie

task, klasa (środowisko uruchomieniowe współbieżności)

cancellation_token_source, klasa

cancellation_token, klasa

task_group, klasa

structured_task_group, klasa

parallel_for, funkcja