Pembatalan di PPL

Dokumen ini menjelaskan peran pembatalan di Pustaka Pola Paralel (PPL), cara membatalkan pekerjaan paralel, dan cara menentukan kapan pekerjaan paralel dibatalkan.

Catatan

Runtime menggunakan penanganan pengecualian untuk menerapkan pembatalan. Jangan menangkap atau menangani pengecualian ini dalam kode Anda. Selain itu, kami sarankan Anda menulis kode yang aman pengecualian di badan fungsi untuk tugas Anda. Misalnya, Anda dapat menggunakan pola Resource Acquisition Is Initialization (RAII) untuk memastikan bahwa sumber daya ditangani dengan benar saat pengecualian dilemparkan dalam isi tugas. Untuk contoh lengkap yang menggunakan pola RAII untuk membersihkan sumber daya dalam tugas yang dapat dibatalkan, lihat Panduan: Menghapus Pekerjaan dari Utas Antarmuka Pengguna.

Poin Penting

Dalam Dokumen ini

Pohon Kerja Paralel

PPL menggunakan tugas dan grup tugas untuk mengelola tugas dan komputasi terperintah. Anda dapat menumpuk grup tugas untuk membentuk pohon pekerjaan paralel. Ilustrasi berikut menunjukkan pohon kerja paralel. Dalam ilustrasi ini, tg1 dan tg2 mewakili grup tugas; t1, , t2t3, t4, dan t5 mewakili pekerjaan yang dilakukan grup tugas.

A parallel work tree.

Contoh berikut menunjukkan kode yang diperlukan untuk membuat pohon dalam ilustrasi. Dalam contoh ini, tg1 dan tg2 merupakan konkurensi::structured_task_group objek; t1, , t2, t3t4, dan t5 konkurensi::task_handle objek.

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

Anda juga dapat menggunakan kelas konkurensi::task_group untuk membuat pohon kerja serupa. Kelas konkurensi::tugas juga mendukung gagasan pohon pekerjaan. Namun, task pohon adalah pohon dependensi. Di pohon task , pekerjaan di masa mendatang selesai setelah pekerjaan saat ini. Di pohon grup tugas, pekerjaan internal selesai sebelum pekerjaan luar. Untuk informasi selengkapnya tentang perbedaan antara tugas dan grup tugas, lihat Paralelisme Tugas.

[Atas]

Membatalkan Tugas Paralel

Ada beberapa cara untuk membatalkan pekerjaan paralel. Cara yang disukai adalah dengan menggunakan token pembatalan. Grup tugas juga mendukung metode konkurensi::task_group::cancel dan metode konkurensi::structured_task_group::cancel . Cara terakhir adalah dengan melemparkan pengecualian dalam isi fungsi kerja tugas. Apa pun metode yang Anda pilih, pahami bahwa pembatalan tidak segera terjadi. Meskipun pekerjaan baru tidak dimulai jika tugas atau grup tugas dibatalkan, pekerjaan aktif harus memeriksa dan menanggapi pembatalan.

Untuk contoh selengkapnya yang membatalkan tugas paralel, lihat Panduan: Koneksi Menggunakan Tugas dan Permintaan HTTP XML, Cara: Menggunakan Pembatalan untuk Memisahkan dari Perulangan Paralel, dan Cara: Menggunakan Penanganan Pengecualian untuk Memisahkan dari Perulangan Paralel.

Menggunakan Token Pembatalan untuk Membatalkan Pekerjaan Paralel

Kelas task, task_group, dan structured_task_group mendukung pembatalan melalui penggunaan token pembatalan. PPL mendefinisikan kelas konkurensi::cancellation_token_source dan konkurensi::cancellation_token untuk tujuan ini. Saat Anda menggunakan token pembatalan untuk membatalkan pekerjaan, runtime tidak memulai pekerjaan baru yang berlangganan token tersebut. Pekerjaan yang sudah aktif dapat menggunakan fungsi anggota is_canceled untuk memantau token pembatalan dan berhenti kapan bisa.

Untuk memulai pembatalan, panggil metode konkurensi::cancellation_token_source::cancel . Anda menanggapi pembatalan dengan cara berikut:

  • Untuk task objek, gunakan fungsi konkurensi::cancel_current_task . cancel_current_task membatalkan tugas saat ini dan salah satu kelanjutan berbasis nilainya. (Ini tidak membatalkan token pembatalan yang terkait dengan tugas atau kelanjutannya.)

  • Untuk grup tugas dan algoritma paralel, gunakan fungsi konkurensi::is_current_task_group_canceling untuk mendeteksi pembatalan dan mengembalikan sesegera mungkin dari isi tugas ketika fungsi ini mengembalikan true. (Jangan panggil cancel_current_task dari grup tugas.)

Contoh berikut menunjukkan pola dasar pertama untuk pembatalan tugas. Isi tugas terkadang memeriksa pembatalan di dalam perulangan.

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

Fungsi melempar cancel_current_task ; oleh karena itu, Anda tidak perlu secara eksplisit kembali dari perulangan atau fungsi saat ini.

Tip

Atau, Anda dapat memanggil fungsi konkurensi::interruption_point alih-alih cancel_current_task.

Penting untuk memanggil cancel_current_task saat Anda menanggapi pembatalan karena mentransisikan tugas ke status dibatalkan. Jika Anda mengembalikan lebih awal alih-alih memanggil cancel_current_task, operasi beralih ke status selesai dan kelanjutan berbasis nilai apa pun dijalankan.

Perhatian

Jangan pernah melempar task_canceled dari kode Anda. Hubungi cancel_current_task sebagai gantinya.

Ketika tugas berakhir dalam status dibatalkan, metode konkurensi::task::get melempar konkurensi::task_canceled. (Sebaliknya, konkurensi::task::wait mengembalikan task_status::canceled dan tidak melemparkan.) Contoh berikut mengilustrasikan perilaku ini untuk kelanjutan berbasis tugas. Kelanjutan berbasis tugas selalu dipanggil, bahkan ketika tugas antecedent dibatalkan.

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

Karena kelanjutan berbasis nilai mewarisi token tugas antecedent mereka kecuali dibuat dengan token eksplisit, kelanjutan segera memasuki status dibatalkan bahkan ketika tugas antecedent masih dijalankan. Oleh karena itu, pengecualian apa pun yang dilemparkan oleh tugas antecedent setelah pembatalan tidak disebarluaskan ke tugas kelanjutan. Pembatalan selalu mengambil alih status tugas antecedent. Contoh berikut menyerupai sebelumnya, tetapi menggambarkan perilaku untuk kelanjutan berbasis nilai.

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

Perhatian

Jika Anda tidak meneruskan token pembatalan ke task konstruktor atau fungsi konkurensi::create_task , tugas tersebut tidak dapat dibatalkan. Selain itu, Anda harus meneruskan token pembatalan yang sama ke konstruktor tugas berlapis apa pun (yaitu, tugas yang dibuat dalam isi tugas lain) untuk membatalkan semua tugas secara bersamaan.

Anda mungkin ingin menjalankan kode arbitrer saat token pembatalan dibatalkan. Misalnya, jika pengguna Anda memilih tombol Batalkan pada antarmuka pengguna untuk membatalkan operasi, Anda dapat menonaktifkan tombol tersebut hingga pengguna memulai operasi lain. Contoh berikut menunjukkan cara menggunakan metode konkurensi::cancellation_token::register_callback untuk mendaftarkan fungsi panggilan balik yang berjalan saat token pembatalan dibatalkan.

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

Dokumen Paralelisme Tugas menjelaskan perbedaan antara kelanjutan berbasis nilai dan berbasis tugas. Jika Anda tidak memberikan cancellation_token objek ke tugas kelanjutan, kelanjutan mewarisi token pembatalan dari tugas anestesi dengan cara berikut:

  • Kelanjutan berbasis nilai selalu mewarisi token pembatalan tugas antecedent.

  • Kelanjutan berbasis tugas tidak pernah mewarisi token pembatalan tugas antecedent. Satu-satunya cara untuk membuat kelanjutan berbasis tugas dapat dibatalkan adalah dengan secara eksplisit meneruskan token pembatalan.

Perilaku ini tidak dipengaruhi oleh tugas yang rusak (yaitu, perilaku yang melemparkan pengecualian). Dalam hal ini, kelanjutan berbasis nilai dibatalkan; kelanjutan berbasis tugas tidak dibatalkan.

Perhatian

Tugas yang dibuat dalam tugas lain (dengan kata lain, tugas berlapis) tidak mewarisi token pembatalan tugas induk. Hanya kelanjutan berbasis nilai yang mewarisi token pembatalan tugas antecedent-nya.

Tip

Gunakan metode konkurensi::cancellation_token::none saat Anda memanggil konstruktor atau fungsi yang mengambil cancellation_token objek dan Anda tidak ingin operasi dapat dibatalkan.

Anda juga dapat memberikan token pembatalan kepada konstruktor task_group objek atau structured_task_group . Aspek penting dari ini adalah bahwa grup tugas turunan mewarisi token pembatalan ini. Misalnya yang menunjukkan konsep ini dengan menggunakan fungsi konkurensi::run_with_cancellation_token untuk dijalankan untuk memanggil parallel_for, lihat Membatalkan Algoritma Paralel nanti dalam dokumen ini.

[Atas]

Token Pembatalan dan Komposisi Tugas

Fungsi konkurensi::when_all dan konkurensi::when_any dapat membantu Anda menyusun beberapa tugas untuk menerapkan pola umum. Bagian ini menjelaskan cara kerja fungsi ini dengan token pembatalan.

Saat Anda memberikan token pembatalan ke fungsi dan when_any , fungsi tersebut when_all hanya membatalkan ketika token pembatalan tersebut dibatalkan atau ketika salah satu tugas peserta berakhir dalam status dibatalkan atau melemparkan pengecualian.

Fungsi ini when_all mewarisi token pembatalan dari setiap tugas yang menyusun operasi keseluruhan ketika Anda tidak memberikan token pembatalan ke dalamnya. Tugas yang dikembalikan dari when_all dibatalkan ketika salah satu token ini dibatalkan dan setidaknya salah satu tugas peserta belum dimulai atau sedang berjalan. Perilaku serupa terjadi ketika salah satu tugas melemparkan pengecualian - tugas yang dikembalikan dari when_all segera dibatalkan dengan pengecualian tersebut.

Runtime memilih token pembatalan untuk tugas yang dikembalikan dari when_any fungsi ketika tugas tersebut selesai. Jika tidak ada tugas peserta yang selesai dalam status selesai dan satu atau beberapa tugas melemparkan pengecualian, salah satu tugas yang dilemparkan dipilih untuk menyelesaikan when_any dan tokennya dipilih sebagai token untuk tugas akhir. Jika lebih dari satu tugas selesai dalam status selesai, tugas yang dikembalikan dari when_any tugas berakhir dalam status selesai. Runtime mencoba memilih tugas yang selesai yang tokennya tidak dibatalkan pada saat penyelesaian sehingga tugas yang dikembalikan dari when_any tidak segera dibatalkan meskipun tugas eksekusi lainnya mungkin selesai di titik selanjutnya.

[Atas]

Menggunakan Metode pembatalan untuk Membatalkan Pekerjaan Paralel

Metode concurrency::task_group::cancel dan concurrency::structured_task_group::cancel mengatur grup tugas ke status dibatalkan. Setelah Anda memanggil cancel, grup tugas tidak memulai tugas di masa mendatang. Metode cancel dapat dipanggil oleh beberapa tugas anak. Tugas yang dibatalkan menyebabkan konkurensi::task_group::wait dan concurrency::structured_task_group::wait methods to return concurrency::canceled.

Jika grup tugas dibatalkan, panggilan dari setiap tugas anak ke dalam runtime dapat memicu titik gangguan, yang menyebabkan runtime untuk melempar dan menangkap jenis pengecualian internal untuk membatalkan tugas aktif. Runtime Konkurensi tidak menentukan titik gangguan tertentu; mereka dapat terjadi dalam panggilan apa pun ke runtime. Runtime harus menangani pengecualian yang dilemparkannya untuk melakukan pembatalan. Oleh karena itu, jangan menangani pengecualian yang tidak diketahui dalam isi tugas.

Jika tugas anak melakukan operasi yang memakan waktu dan tidak memanggil runtime, tugas tersebut harus secara berkala memeriksa pembatalan dan keluar secara tepat waktu. Contoh berikut menunjukkan salah satu cara untuk menentukan kapan pekerjaan dibatalkan. Tugas t4 membatalkan grup tugas induk ketika mengalami kesalahan. Tugas t5 terkadang memanggil structured_task_group::is_canceling metode untuk memeriksa pembatalan. Jika grup tugas induk dibatalkan, tugas t5 mencetak pesan dan keluar.

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

Contoh ini memeriksa pembatalan pada setiap perulangan 100th dari perulangan tugas. Frekuensi saat Anda memeriksa pembatalan tergantung pada jumlah pekerjaan yang dilakukan tugas Anda dan seberapa cepat Anda membutuhkan tugas untuk merespons pembatalan.

Jika Anda tidak memiliki akses ke objek grup tugas induk, panggil fungsi konkurensi::is_current_task_group_canceling untuk menentukan apakah grup tugas induk dibatalkan.

Metode cancel ini hanya memengaruhi tugas anak. Misalnya, jika Anda membatalkan grup tg1 tugas dalam ilustrasi pohon kerja paralel, semua tugas di pohon (t1, , t2t3, t4, dan t5) terpengaruh. Jika Anda membatalkan grup tugas berlapis, tg2, hanya tugas t4 dan t5 yang terpengaruh.

Saat Anda memanggil metode , cancel semua grup tugas anak juga dibatalkan. Namun, pembatalan tidak memengaruhi induk grup tugas dalam pohon kerja paralel. Contoh berikut menunjukkan ini dengan membangun ilustrasi pohon kerja paralel.

Contoh pertama dari ini membuat fungsi kerja untuk tugas t4, yang merupakan anak dari grup tg2tugas . Fungsi kerja memanggil fungsi work dalam perulangan. Jika ada panggilan untuk work gagal, tugas membatalkan grup tugas induknya. Ini menyebabkan grup tg2 tugas memasuki status dibatalkan, tetapi tidak membatalkan grup tg1tugas .

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

Contoh kedua ini menyerupai yang pertama, kecuali bahwa tugas membatalkan grup tg1tugas . Ini mempengaruhi semua tugas di pohon (t1, , t2, t3t4, dan 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;
      }
   }   
});

Kelas structured_task_group ini tidak aman untuk utas. Oleh karena itu, tugas anak yang memanggil metode objek induknya structured_task_group menghasilkan perilaku yang tidak ditentukan. Pengecualian untuk aturan ini adalah structured_task_group::cancel metode dan konkurensi::structured_task_group::is_canceling . Tugas anak dapat memanggil metode ini untuk membatalkan grup tugas induk dan memeriksa pembatalan.

Perhatian

Meskipun Anda dapat menggunakan token pembatalan untuk membatalkan pekerjaan yang dilakukan oleh grup tugas yang berjalan sebagai anak dari task objek, Anda tidak dapat menggunakan task_group::cancel metode atau structured_task_group::cancel untuk membatalkan task objek yang berjalan dalam grup tugas.

[Atas]

Menggunakan Pengecualian untuk Membatalkan Pekerjaan Paralel

Penggunaan token pembatalan dan cancel metode ini lebih efisien daripada penanganan pengecualian saat membatalkan pohon kerja paralel. Token pembatalan dan cancel metode membatalkan tugas dan tugas turunan apa pun dengan cara top-down. Sebaliknya, penanganan pengecualian bekerja dengan cara bawah ke atas dan harus membatalkan setiap grup tugas turunan secara independen saat pengecualian menyebar ke atas. Penanganan Pengecualian topik menjelaskan bagaimana Runtime Konkurensi menggunakan pengecualian untuk mengomunikasikan kesalahan. Namun, tidak semua pengecualian menunjukkan kesalahan. Misalnya, algoritma pencarian mungkin membatalkan tugas terkait saat menemukan hasilnya. Namun, seperti disebutkan sebelumnya, penanganan pengecualian kurang efisien daripada menggunakan cancel metode untuk membatalkan pekerjaan paralel.

Perhatian

Kami menyarankan agar Anda menggunakan pengecualian untuk membatalkan pekerjaan paralel hanya jika perlu. Token pembatalan dan metode grup cancel tugas lebih efisien dan kurang rentan terhadap kesalahan.

Saat Anda melemparkan pengecualian dalam isi fungsi kerja yang Anda teruskan ke grup tugas, runtime menyimpan pengecualian dan marshal pengecualian ke konteks yang menunggu grup tugas selesai. Seperti halnya cancel metode , runtime membuang tugas apa pun yang belum dimulai, dan tidak menerima tugas baru.

Contoh ketiga ini menyerupai yang kedua, kecuali bahwa tugas t4 melemparkan pengecualian untuk membatalkan grup tg2tugas . Contoh ini menggunakan try-catch blok untuk memeriksa pembatalan ketika grup tg2 tugas menunggu tugas turunannya selesai. Seperti contoh pertama, ini menyebabkan grup tg2 tugas memasuki status dibatalkan, tetapi tidak membatalkan grup tg1tugas .

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

Contoh keempat ini menggunakan penanganan pengecualian untuk membatalkan seluruh pohon kerja. Contoh menangkap pengecualian ketika grup tg1 tugas menunggu tugas turunannya selesai alih-alih ketika grup tg2 tugas menunggu tugas turunannya. Seperti contoh kedua, ini menyebabkan kedua grup tugas di pohon, tg1 dan tg2, memasuki status dibatalkan.

// 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 Karena metode dan structured_task_group::wait melempar ketika tugas anak melemparkan pengecualian, Anda tidak menerima nilai pengembalian dari mereka.

[Atas]

Membatalkan Algoritma Paralel

Algoritma paralel dalam PPL, misalnya, parallel_for, dibangun berdasarkan grup tugas. Oleh karena itu, Anda dapat menggunakan banyak teknik yang sama untuk membatalkan algoritma paralel.

Contoh berikut mengilustrasikan beberapa cara untuk membatalkan algoritma paralel.

Contoh berikut menggunakan run_with_cancellation_token fungsi untuk memanggil parallel_for algoritma. Fungsi ini run_with_cancellation_token mengambil token pembatalan sebagai argumen dan memanggil fungsi kerja yang disediakan secara sinkron. Karena algoritma paralel dibangun berdasarkan tugas, algoritma tersebut mewarisi token pembatalan tugas induk. Oleh karena itu, parallel_for dapat menanggapi pembatalan.

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

Contoh berikut menggunakan metode konkurensi::structured_task_group::run_and_wait untuk memanggil parallel_for algoritma. Metode structured_task_group::run_and_wait menunggu tugas yang disediakan selesai. Objek structured_task_group memungkinkan fungsi kerja untuk membatalkan tugas.

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

Contoh ini menghasilkan output berikut.

The task group status is: canceled.

Contoh berikut menggunakan penanganan pengecualian untuk membatalkan perulangan parallel_for . Runtime marshals pengecualian untuk konteks panggilan.

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

Contoh ini menghasilkan output berikut.

Caught 50

Contoh berikut menggunakan bendera Boolean untuk mengoordinasikan pembatalan dalam perulangan parallel_for . Setiap tugas berjalan karena contoh ini tidak menggunakan cancel metode atau penanganan pengecualian untuk membatalkan serangkaian tugas secara keseluruhan. Oleh karena itu, teknik ini dapat memiliki lebih banyak overhead komputasi daripada mekanisme pembatalan.

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

Setiap metode pembatalan memiliki keuntungan dibandingkan yang lain. Pilih metode yang sesuai dengan kebutuhan spesifik Anda.

[Atas]

Kapan Tidak Menggunakan Pembatalan

Penggunaan pembatalan sesuai ketika setiap anggota grup tugas terkait dapat keluar tepat waktu. Namun, ada beberapa skenario di mana pembatalan mungkin tidak sesuai untuk aplikasi Anda. Misalnya, karena pembatalan tugas bersifat kooperatif, kumpulan tugas keseluruhan tidak akan dibatalkan jika ada tugas individual yang diblokir. Misalnya, jika satu tugas belum dimulai, tetapi membuka blokir tugas aktif lainnya, tugas tidak akan dimulai jika grup tugas dibatalkan. Ini dapat menyebabkan kebuntuan terjadi di aplikasi Anda. Contoh kedua di mana penggunaan pembatalan mungkin tidak sesuai adalah ketika tugas dibatalkan, tetapi tugas anaknya melakukan operasi penting, seperti membebaskan sumber daya. Karena kumpulan tugas keseluruhan dibatalkan ketika tugas induk dibatalkan, operasi tersebut tidak akan dijalankan. Untuk contoh yang mengilustrasikan poin ini, lihat bagian Memahami bagaimana Penanganan Pembatalan dan Pengecualian Memengaruhi Penghancuran Objek di praktik terbaik dalam topik Pustaka Pola Paralel.

[Atas]

Judul Deskripsi
Cara: Menggunakan Pembatalan untuk Memutuskan dari Perulangan Paralel Memperlihatkan cara menggunakan pembatalan untuk menerapkan algoritma pencarian paralel.
Cara: Menggunakan Penanganan Pengecualian untuk Memutuskan dari Perulangan Paralel Memperlihatkan cara menggunakan task_group kelas untuk menulis algoritma pencarian untuk struktur pohon dasar.
Penanganan Pengecualian Menjelaskan bagaimana runtime menangani pengecualian yang dilemparkan oleh grup tugas, tugas ringan, dan agen asinkron, dan cara merespons pengecualian dalam aplikasi Anda.
Paralelisme Tugas Menjelaskan bagaimana tugas terkait dengan grup tugas dan bagaimana Anda dapat menggunakan tugas yang tidak terstruktur dan terstruktur dalam aplikasi Anda.
Algoritma Paralel Menjelaskan algoritma paralel, yang secara bersamaan melakukan pekerjaan pada pengumpulan data
Parallel Patterns Library (PPL) Menyediakan gambaran umum Pustaka Pola Paralel.

Referensi

task Class (Concurrency Runtime)

Kelas cancellation_token_source

Kelas cancellation_token

Kelas task_group

Kelas structured_task_group

Fungsi parallel_for