Penanganan Pengecualian dalam Runtime Konkurensi

Runtime Konkurensi menggunakan penanganan pengecualian C++ untuk mengomunikasikan banyak jenis kesalahan. Kesalahan ini termasuk penggunaan runtime yang tidak valid, kesalahan runtime seperti kegagalan untuk memperoleh sumber daya, dan kesalahan yang terjadi dalam fungsi kerja yang Anda berikan ke tugas dan grup tugas. Saat tugas atau grup tugas melemparkan pengecualian, runtime memegang pengecualian tersebut dan melakukan marsekal ke konteks yang menunggu tugas atau grup tugas selesai. Untuk komponen seperti tugas dan agen ringan, runtime tidak mengelola pengecualian untuk Anda. Dalam kasus ini, Anda harus menerapkan mekanisme penanganan pengecualian Anda sendiri. Topik ini menjelaskan bagaimana runtime menangani pengecualian yang dilemparkan oleh tugas, grup tugas, tugas ringan, dan agen asinkron, dan cara merespons pengecualian dalam aplikasi Anda.

Poin Penting

  • Saat tugas atau grup tugas melemparkan pengecualian, runtime memegang pengecualian tersebut dan melakukan marsekal ke konteks yang menunggu tugas atau grup tugas selesai.

  • Jika memungkinkan, kelilingi setiap panggilan ke konkurensi::task::get dan concurrency::task::wait dengan try/catch blok untuk menangani kesalahan yang dapat Anda pulihkan. Runtime menghentikan aplikasi jika tugas melemparkan pengecualian dan pengecualian tersebut tidak tertangkap oleh tugas, salah satu kelanjutannya, atau aplikasi utama.

  • Kelanjutan berbasis tugas selalu berjalan; tidak masalah apakah tugas antecedent berhasil diselesaikan, melemparkan pengecualian, atau dibatalkan. Kelanjutan berbasis nilai tidak berjalan jika tugas antecedent melempar atau membatalkan.

  • Karena kelanjutan berbasis tugas selalu berjalan, pertimbangkan apakah akan menambahkan kelanjutan berbasis tugas di akhir rantai kelanjutan Anda. Ini dapat membantu menjamin bahwa kode Anda mengamati semua pengecualian.

  • Runtime melempar konkurensi::task_canceled saat Anda memanggil konkurensi::task::get dan tugas tersebut dibatalkan.

  • Runtime tidak mengelola pengecualian untuk tugas dan agen yang ringan.

Dalam Dokumen ini

Tugas dan Kelanjutan

Bagian ini menjelaskan bagaimana runtime menangani pengecualian yang dilemparkan oleh objek konkurensi::tugas dan kelanjutannya. Untuk informasi selengkapnya tentang model tugas dan kelanjutan, lihat Paralelisme Tugas.

Saat Anda melemparkan pengecualian dalam isi fungsi kerja yang Anda teruskan ke task objek, runtime menyimpan pengecualian tersebut dan menyimpannya ke konteks yang memanggil konkurensi::task::get atau konkurensi::task::wait. Dokumen Paralelisme Tugas menjelaskan kelanjutan berbasis tugas versus berbasis nilai, tetapi untuk meringkas, kelanjutan berbasis nilai mengambil parameter jenis T dan kelanjutan berbasis tugas mengambil parameter jenis task<T>. Jika tugas yang dilemparkan memiliki satu atau beberapa kelanjutan berbasis nilai, kelanjutan tersebut tidak dijadwalkan untuk dijalankan. Contoh berikut mengilustrasikan perilaku ini:

// eh-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]
    {
        throw exception();
    });
    
    // Create a continuation that prints its input value.
    auto continuation = t.then([]
    {
        // We do not expect this task to run because
        // the antecedent task threw.
        wcout << L"In continuation task..." << endl;
    });

    // Wait for the continuation to finish and handle any 
    // error that occurs.
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        continuation.wait();

        // Alternatively, call get() to produce the same result.
        //continuation.get();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception.
*/

Kelanjutan berbasis tugas memungkinkan Anda menangani pengecualian apa pun yang dilemparkan oleh tugas antecedent. Kelanjutan berbasis tugas selalu berjalan; tidak masalah apakah tugas berhasil diselesaikan, melemparkan pengecualian, atau dibatalkan. Ketika tugas melemparkan pengecualian, kelanjutan berbasis tugasnya dijadwalkan untuk dijalankan. Contoh berikut menunjukkan tugas yang selalu dilemparkan. Tugas ini memiliki dua kelanjutan; satu berbasis nilai dan yang lainnya berbasis tugas. Pengecualian berbasis tugas selalu berjalan, dan oleh karena itu dapat menangkap pengecualian yang dilemparkan oleh tugas antecedent. Ketika contoh menunggu kedua kelanjutan selesai, pengecualian dilemparkan lagi karena pengecualian tugas selalu dilemparkan ketika task::get atau task::wait dipanggil.

// eh-continuations.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{    
    wcout << L"Running a task..." << endl;
    // Create a task that throws.
    auto t = create_task([]() -> int
    {
        throw exception();
        return 42;
    });

    //
    // Attach two continuations to the task. The first continuation is  
    // value-based; the second is task-based.

    // Value-based continuation.
    auto c1 = t.then([](int n)
    {
        // We don't expect to get here because the antecedent 
        // task always throws.
        wcout << L"Received " << n << L'.' << endl;
    });

    // Task-based continuation.
    auto c2 = t.then([](task<int> previousTask)
    {
        // We do expect to get here because task-based continuations
        // are scheduled even when the antecedent task throws.
        try
        {
            wcout << L"Received " << previousTask.get() << L'.' << endl;
        }
        catch (const exception& e)
        {
            wcout << L"Caught exception from previous task." << endl;
        }
    });

    // Wait for the continuations to finish.
    try
    {
        wcout << L"Waiting for tasks to finish..." << endl;
        (c1 && c2).wait();
    }
    catch (const exception& e)
    {
        wcout << L"Caught exception while waiting for all tasks to finish." << endl;
    }
}
/* Output:
    Running a task...
    Waiting for tasks to finish...
    Caught exception from previous task.
    Caught exception while waiting for all tasks to finish.
*/

Kami menyarankan agar Anda menggunakan kelanjutan berbasis tugas untuk menangkap pengecualian yang dapat Anda tangani. Karena kelanjutan berbasis tugas selalu berjalan, pertimbangkan apakah akan menambahkan kelanjutan berbasis tugas di akhir rantai kelanjutan Anda. Ini dapat membantu menjamin bahwa kode Anda mengamati semua pengecualian. Contoh berikut menunjukkan rantai kelanjutan berbasis nilai dasar. Tugas ketiga dalam rantai melemparkan, dan oleh karena itu kelanjutan berbasis nilai apa pun yang mengikutinya tidak dijalankan. Namun, kelanjutan akhir berbasis tugas, dan oleh karena itu selalu berjalan. Kelanjutan akhir ini menangani pengecualian yang dilemparkan oleh tugas ketiga.

Kami menyarankan agar Anda menangkap pengecualian paling spesifik yang Anda bisa. Anda dapat menghilangkan kelanjutan berbasis tugas akhir ini jika Anda tidak memiliki pengecualian khusus untuk ditangkap. Pengecualian apa pun akan tetap tidak tertangani dan dapat mengakhiri aplikasi.

// eh-task-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    int n = 1;
    create_task([n]
    {
        wcout << L"In first task. n = ";
        wcout << n << endl;
        
        return n * 2;

    }).then([](int n)
    {
        wcout << L"In second task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](int n)
    {
        wcout << L"In third task. n = ";
        wcout << n << endl;

        // This task throws.
        throw exception();
        // Not reached.
        return n * 2;

    }).then([](int n)
    {
        // This continuation is not run because the previous task throws.
        wcout << L"In fourth task. n = ";
        wcout << n << endl;

        return n * 2;

    }).then([](task<int> previousTask)
    {
        // This continuation is run because it is task-based.
        try
        {
            // The call to task::get rethrows the exception.
            wcout << L"In final task. result = ";
            wcout << previousTask.get() << endl;
        }
        catch (const exception&)
        {
            wcout << L"<exception>" << endl;
        }
    }).wait();
}
/* Output:
    In first task. n = 1
    In second task. n = 2
    In third task. n = 4
    In final task. result = <exception>
*/

Tip

Anda dapat menggunakan metode konkurensi::task_completion_event::set_exception untuk mengaitkan pengecualian dengan peristiwa penyelesaian tugas. Dokumen ParalelismeTugas menjelaskan kelas konkurensi::task_completion_event secara lebih rinci.

konkurensi::task_canceled adalah jenis pengecualian runtime penting yang berkaitan dengan task. Runtime muncul task_canceled saat Anda memanggil task::get dan tugas tersebut dibatalkan. (Sebaliknya, task::wait mengembalikan task_status::dibatalkan dan tidak dilemparkan.) Anda dapat menangkap dan menangani pengecualian ini dari kelanjutan berbasis tugas atau saat Anda memanggil task::get. Untuk informasi selengkapnya tentang pembatalan tugas, lihat Pembatalan di PPL.

Perhatian

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

Runtime menghentikan aplikasi jika tugas melemparkan pengecualian dan pengecualian tersebut tidak tertangkap oleh tugas, salah satu kelanjutannya, atau aplikasi utama. Jika aplikasi mengalami crash, Anda dapat mengonfigurasi Visual Studio untuk berhenti saat pengecualian C++ dilemparkan. Setelah Anda mendiagnosis lokasi pengecualian yang tidak tertangani, gunakan kelanjutan berbasis tugas untuk menanganinya.

Bagian Pengecualian yang Dilemparkan oleh Runtime dalam dokumen ini menjelaskan cara bekerja dengan pengecualian runtime secara lebih rinci.

[Atas]

Grup Tugas dan Algoritma Paralel

Bagian ini menjelaskan bagaimana runtime menangani pengecualian yang dilemparkan oleh grup tugas. Bagian ini juga berlaku untuk algoritma paralel seperti konkurensi::p arallel_for, karena algoritma ini dibangun pada grup tugas.

Perhatian

Pastikan Anda memahami efek yang dimiliki pengecualian pada tugas dependen. Untuk praktik yang direkomendasikan tentang cara menggunakan penanganan pengecualian dengan tugas atau algoritma paralel, lihat bagian Memahami bagaimana Penanganan Pembatalan dan Pengecualian Memengaruhi Penghancuran Objek dalam Praktik Terbaik dalam topik Pustaka Pola Paralel.

Untuk informasi selengkapnya tentang grup tugas, lihat Paralelisme Tugas. Untuk informasi selengkapnya tentang algoritma paralel, lihat Algoritma Paralel.

Saat Anda memberikan pengecualian dalam isi fungsi kerja yang Anda teruskan ke objek konkurensi::task_group atau konkurensi::structured_task_group , runtime menyimpan pengecualian dan marsekalnya ke konteks yang memanggil konkurensi::task_group::wait, concurrency::structured_task_group::wait, concurrency::task_group::run_and_wait, atau concurrency:: structured_task_group::run_and_wait. Runtime juga menghentikan semua tugas aktif yang ada di grup tugas (termasuk tugas dalam grup tugas turunan) dan membuang tugas apa pun yang belum dimulai.

Contoh berikut menunjukkan struktur dasar fungsi kerja yang melempar pengecualian. Contoh menggunakan task_group objek untuk mencetak nilai dua point objek secara paralel. Fungsi print_point kerja mencetak nilai point objek ke konsol. Fungsi kerja melemparkan pengecualian jika nilai input adalah NULL. Runtime menyimpan pengecualian ini dan menyimpannya ke konteks yang memanggil task_group::wait.

// eh-task-group.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// Defines a basic point with X and Y coordinates.
struct point
{
   int X;
   int Y;
};

// Prints the provided point object to the console.
void print_point(point* pt)
{
   // Throw an exception if the value is NULL.
   if (pt == NULL)
   {
      throw exception("point is NULL.");
   }

   // Otherwise, print the values of the point.
   wstringstream ss;
   ss << L"X = " << pt->X << L", Y = " << pt->Y << endl;
   wcout << ss.str();
}

int wmain()
{
   // Create a few point objects.
   point pt = {15, 30};
   point* pt1 = &pt;
   point* pt2 = NULL;

   // Use a task group to print the values of the points.
   task_group tasks;

   tasks.run([&] {
      print_point(pt1);
   });

   tasks.run([&] {
      print_point(pt2);
   });

   // Wait for the tasks to finish. If any task throws an exception,
   // the runtime marshals it to the call to wait.
   try
   {
      tasks.wait();
   }
   catch (const exception& e)
   {
      wcerr << L"Caught exception: " << e.what() << endl;
   }
}

Contoh ini menghasilkan output berikut.

X = 15, Y = 30Caught exception: point is NULL.

Untuk contoh lengkap yang menggunakan penanganan pengecualian dalam grup tugas, lihat Cara: Menggunakan Penanganan Pengecualian untuk Memisahkan dari Perulangan Paralel.

[Atas]

Pengecualian yang Dilemparkan oleh Runtime

Pengecualian dapat dihasilkan dari panggilan ke runtime. Sebagian besar jenis pengecualian, kecuali untuk konkurensi::task_canceled dan konkurensi::operation_timed_out, menunjukkan kesalahan pemrograman. Kesalahan ini biasanya tidak dapat dipulihkan, dan oleh karena itu tidak boleh ditangkap atau ditangani oleh kode aplikasi. Kami menyarankan agar Anda hanya menangkap atau menangani kesalahan yang tidak dapat dipulihkan dalam kode aplikasi Anda saat Anda perlu mendiagnosis kesalahan pemrograman. Namun, memahami jenis pengecualian yang ditentukan oleh runtime dapat membantu Anda mendiagnosis kesalahan pemrograman.

Mekanisme penanganan pengecualian sama untuk pengecualian yang dilemparkan oleh runtime sebagai pengecualian yang dilemparkan oleh fungsi kerja. Misalnya, fungsi konkurensi::receive muncul operation_timed_out ketika tidak menerima pesan dalam periode waktu yang ditentukan. Jika receive melemparkan pengecualian dalam fungsi kerja yang Anda teruskan ke grup tugas, runtime menyimpan pengecualian itu dan marsekalnya ke konteks yang memanggil task_group::wait, , structured_task_group::wait, task_group::run_and_waitatau structured_task_group::run_and_wait.

Contoh berikut menggunakan algoritma concurrency::p arallel_invoke untuk menjalankan dua tugas secara paralel. Tugas pertama menunggu lima detik lalu mengirim pesan ke buffer pesan. Tugas kedua menggunakan receive fungsi untuk menunggu tiga detik untuk menerima pesan dari buffer pesan yang sama. Fungsi melemparkan receiveoperation_timed_out jika tidak menerima pesan dalam periode waktu.

// eh-time-out.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   single_assignment<int> buffer;
   int result;

   try
   {
      // Run two tasks in parallel.
      parallel_invoke(
         // This task waits 5 seconds and then sends a message to 
         // the message buffer.
         [&] {
            wait(5000); 
            send(buffer, 42);
         },
         // This task waits 3 seconds to receive a message.
         // The receive function throws operation_timed_out if it does 
         // not receive a message in the specified time period.
         [&] {
            result = receive(buffer, 3000);
         }
      );

      // Print the result.
      wcout << L"The result is " << result << endl;
   }
   catch (operation_timed_out&)
   {
      wcout << L"The operation timed out." << endl;
   }
}

Contoh ini menghasilkan output berikut.

The operation timed out.

Untuk mencegah penghentian abnormal aplikasi Anda, pastikan kode Anda menangani pengecualian saat dipanggil ke runtime. Tangani juga pengecualian saat Anda memanggil kode eksternal yang menggunakan Runtime Konkurensi, misalnya, pustaka pihak ketiga.

[Atas]

Beberapa Pengecualian

Jika tugas atau algoritma paralel menerima beberapa pengecualian, runtime marshal hanya satu dari pengecualian tersebut ke konteks panggilan. Runtime tidak menjamin pengecualian mana yang dijadikan marshal.

Contoh berikut menggunakan parallel_for algoritma untuk mencetak angka ke konsol. Ini melemparkan pengecualian jika nilai input kurang dari beberapa nilai minimum atau lebih besar dari beberapa nilai maksimum. Dalam contoh ini, beberapa fungsi kerja dapat melemparkan pengecualian.

// eh-multiple.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
   const int min = 0;
   const int max = 10;
   
   // Print values in a parallel_for loop. Use a try-catch block to 
   // handle any exceptions that occur in the loop.
   try
   {
      parallel_for(-5, 20, [min,max](int i)
      {
         // Throw an exeception if the input value is less than the 
         // minimum or greater than the maximum.

         // Otherwise, print the value to the console.

         if (i < min)
         {
            stringstream ss;
            ss << i << ": the value is less than the minimum.";
            throw exception(ss.str().c_str());
         }
         else if (i > max)
         {
            stringstream ss;
            ss << i << ": the value is greater than than the maximum.";
            throw exception(ss.str().c_str());
         }
         else
         {
            wstringstream ss;
            ss << i << endl;
            wcout << ss.str();
         }
      });
   }
   catch (exception& e)
   {
      // Print the error to the console.
      wcerr << L"Caught exception: " << e.what() << endl;
   }  
}

Berikut ini memperlihatkan contoh output untuk contoh ini.

8293104567Caught exception: -5: the value is less than the minimum.

[Atas]

Pembatalan

Tidak semua pengecualian menunjukkan kesalahan. Misalnya, algoritma pencarian mungkin menggunakan penanganan pengecualian untuk menghentikan tugas terkait saat menemukan hasilnya. Untuk informasi selengkapnya tentang cara menggunakan mekanisme pembatalan dalam kode Anda, lihat Pembatalan di PPL.

[Atas]

Tugas Ringan

Tugas ringan adalah tugas yang Anda jadwalkan langsung dari konkurensi::Objek scheduler . Tugas ringan membawa lebih sedikit overhead daripada tugas biasa. Namun, runtime tidak menangkap pengecualian yang dilemparkan oleh tugas ringan. Sebaliknya, pengecualian ditangkap oleh handler pengecualian yang tidak tertangani, yang secara default mengakhiri proses. Oleh karena itu, gunakan mekanisme penanganan kesalahan yang sesuai dalam aplikasi Anda. Untuk informasi selengkapnya tentang tugas ringan, lihat Penjadwal Tugas.

[Atas]

Agen Asinkron

Seperti tugas ringan, runtime tidak mengelola pengecualian yang dilemparkan oleh agen asinkron.

Contoh berikut menunjukkan salah satu cara untuk menangani pengecualian di kelas yang berasal dari konkurensi::agent. Contoh ini mendefinisikan points_agent kelas . Metode membaca points_agent::runpoint objek dari buffer pesan dan mencetaknya ke konsol. Metode ini run melemparkan pengecualian jika menerima NULL penunjuk.

Metode ini run mengelilingi semua pekerjaan dalam blok try-catch . catch Blok menyimpan pengecualian dalam buffer pesan. Aplikasi memeriksa apakah agen mengalami kesalahan dengan membaca dari buffer ini setelah agen selesai.

// eh-agents.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

// Defines a point with x and y coordinates.
struct point
{
   int X;
   int Y;
};

// Informs the agent to end processing.
point sentinel = {0,0};

// An agent that prints point objects to the console.
class point_agent : public agent
{
public:
   explicit point_agent(unbounded_buffer<point*>& points)
      : _points(points)
   { 
   }

   // Retrieves any exception that occurred in the agent.
   bool get_error(exception& e)
   {
      return try_receive(_error, e);
   }

protected:
   // Performs the work of the agent.
   void run()
   {
      // Perform processing in a try block.
      try
      {
         // Read from the buffer until we reach the sentinel value.
         while (true)
         {
            // Read a value from the message buffer.
            point* r = receive(_points);

            // In this example, it is an error to receive a 
            // NULL point pointer. In this case, throw an exception.
            if (r == NULL)
            {
               throw exception("point must not be NULL");
            }
            // Break from the loop if we receive the 
            // sentinel value.
            else if (r == &sentinel)
            {
               break;
            }
            // Otherwise, do something with the point.
            else
            {
               // Print the point to the console.
               wcout << L"X: " << r->X << L" Y: " << r->Y << endl;
            }
         }
      }
      // Store the error in the message buffer.
      catch (exception& e)
      {
         send(_error, e);
      }

      // Set the agent status to done.
      done();
   }

private:
   // A message buffer that receives point objects.
   unbounded_buffer<point*>& _points;

   // A message buffer that stores error information.
   single_assignment<exception> _error;
};

int wmain()
{  
   // Create a message buffer so that we can communicate with
   // the agent.
   unbounded_buffer<point*> buffer;

   // Create and start a point_agent object.
   point_agent a(buffer);
   a.start();

   // Send several points to the agent.
   point r1 = {10, 20};
   point r2 = {20, 30};
   point r3 = {30, 40};

   send(buffer, &r1);
   send(buffer, &r2);
   // To illustrate exception handling, send the NULL pointer to the agent.
   send(buffer, reinterpret_cast<point*>(NULL));
   send(buffer, &r3);
   send(buffer, &sentinel);

   // Wait for the agent to finish.
   agent::wait(&a);
  
   // Check whether the agent encountered an error.
   exception e;
   if (a.get_error(e))
   {
      cout << "error occurred in agent: " << e.what() << endl;
   }
   
   // Print out agent status.
   wcout << L"the status of the agent is: ";
   switch (a.status())
   {
   case agent_created:
      wcout << L"created";
      break;
   case agent_runnable:
      wcout << L"runnable";
      break;
   case agent_started:
      wcout << L"started";
      break;
   case agent_done:
      wcout << L"done";
      break;
   case agent_canceled:
      wcout << L"canceled";
      break;
   default:
      wcout << L"unknown";
      break;
   }
   wcout << endl;
}

Contoh ini menghasilkan output berikut.

X: 10 Y: 20
X: 20 Y: 30
error occurred in agent: point must not be NULL
the status of the agent is: done

trycatch-Karena blok ada di luar perulanganwhile, agen mengakhiri pemrosesan ketika mengalami kesalahan pertama. trycatch-Jika blok berada di dalam perulanganwhile, agen akan berlanjut setelah kesalahan terjadi.

Contoh ini menyimpan pengecualian dalam buffer pesan sehingga komponen lain dapat memantau agen untuk kesalahan saat berjalan. Contoh ini menggunakan objek konkurensi::single_assignment untuk menyimpan kesalahan. Dalam kasus di mana agen menangani beberapa pengecualian, single_assignment kelas hanya menyimpan pesan pertama yang diteruskan ke dalamnya. Untuk menyimpan hanya pengecualian terakhir, gunakan kelas konkurensi::overwrite_buffer . Untuk menyimpan semua pengecualian, gunakan kelas konkurensi::unbounded_buffer . Untuk informasi selengkapnya tentang blok pesan ini, lihat Blok Pesan Asinkron.

Untuk informasi selengkapnya tentang agen asinkron, lihat Agen Asinkron.

[Atas]

Ringkasan

[Atas]

Baca juga

Runtime Konkurensi
Paralelisme Tugas
Algoritma Paralel
Pembatalan di PPL
Tugas Microsoft Azure Scheduler
Agen Asinkron