Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.
Penanganan Kesalahan
try
/catch
/finally
membangun di Java membuatnya sederhana untuk menangani kesalahan dan digunakan di mana-mana. Hal ini memungkinkan Anda untuk mengasosiasikan handler kesalahan untuk blok kode. Secara internal, ini bekerja dengan isian metadata tambahan tentang handler kesalahan pada tumpukan panggilan. Ketika pengecualian dilemparkan, waktu aktif terlihat di tumpukan panggilan untuk handler kesalahan terkait dan memanggil waktu aktif; dan jika tidak ada handler kesalahan yang tepat ditemukan, waktu aktif menyebarkan pengecualian ke atas rantai panggilan.
Ini bekerja dengan baik untuk kode sinkron, tapi penanganan kesalahan dalam program asinkron dan distribusi menimbulkan tantangan tambahan. Sejak panggilan asinkron kembali dengan segera, pemanggil tidak pada tumpukan panggilan ketika kode asinkron mengeksekusi. Ini berarti bahwa pengecualian yang tidak tertangani dalam kode asinkron tidak dapat ditangani oleh pemanggil dengan cara biasa. Biasanya, pengecualian yang berasal dalam kode asinkron ditangani dengan melewati status kesalahan ke panggilan balik yang diteruskan ke metode asinkron. Atau, jika Future<?>
sedang digunakan, itu melaporkan kesalahan saat Anda mencoba mengaksesnya. Ini kurang dari ideal karena kode yang menerima pengecualian (callback atau kode yang menggunakan Future<?>
) tidak memiliki konteks panggilan asli dan mungkin tidak dapat menangani pengecualian secara memadai. Selain itu, dalam sistem asinkron terdistribusi, dengan komponen yang berjalan bersamaan, lebih dari satu kesalahan dapat terjadi secara bersamaan. Kesalahan ini bisa dari berbagai jenis dan tingkat kepelikan dan perlu ditangani dengan tepat.
Membersihkan sumber daya setelah panggilan asinkron juga sulit. Tidak seperti kode sinkron, Anda tidak dapat menggunakan mencoba/menangkap/akhirnya dalam kode panggilan untuk membersihkan sumber daya sejak pekerjaan dimulai di blok coba mungkin masih berlangsung ketika akhirnya blok mengeksekusi.
Kerangka kerja ini menyediakan mekanisme yang membuat penanganan kesalahan dalam kode asinkron terdistribusi mirip dengan, dan hampir sesederhana, Java mencoba/menangkap/akhirnya.
ImageProcessingActivitiesClient activitiesClient = new ImageProcessingActivitiesClientImpl(); public void createThumbnail(final String webPageUrl) { new TryCatchFinally() { @Override protected void doTry() throws Throwable { List<String> images = getImageUrls(webPageUrl); for (String image: images) { Promise<String> localImage = activitiesClient.downloadImage(image); Promise<String> thumbnailFile = activitiesClient.createThumbnail(localImage); activitiesClient.uploadImage(thumbnailFile); } } @Override protected void doCatch(Throwable e) throws Throwable { // Handle exception and rethrow failures LoggingActivitiesClient logClient = new LoggingActivitiesClientImpl(); logClient.reportError(e); throw new RuntimeException("Failed to process images", e); } @Override protected void doFinally() throws Throwable { activitiesClient.cleanUp(); } }; }
Kelas TryCatchFinally
dan varian kelasnya, TryFinally
dan TryCatch
, pekerjaan yang mirip dengan Java try
/catch
/finally
. Menggunakannya, Anda dapat mengasosiasikan handler pengecualian untuk blok kode alur kerja yang dapat mengeksekusi tugas sebagai asinkron dan jarak jauh. Metode doTry()
secara logis setara dengan blok try
. Kerangka kerja secara otomatis mengeksekusi kode di doTry()
. Daftar objek Promise
dapat diteruskan ke konstruktor TryCatchFinally
. Metode doTry
akan dieksekusi ketika semua objek Promise
diteruskan ke konstruktor telah siap. Jika pengecualian dimunculkan oleh kode yang secara asinkron dipanggil dari dalam doTry()
, setiap pekerjaan yang tertunda di doTry()
dibatalkan dan doCatch()
dipanggil untuk menangani pengecualian. Misalnya, dalam daftar di atas, jika downloadImage
melempar pengecualian, maka createThumbnail
dan uploadImage
akan dibatalkan. Akhirnya, doFinally()
dipanggil ketika semua pekerjaan asinkron dilakukan (selesai, gagal, atau dibatalkan). Hal ini dapat digunakan untuk pembersihan sumber daya. Anda juga dapat menyusun kelas ini untuk menyesuaikan dengan kebutuhan Anda.
Ketika pengecualian dilaporkan dalam doCatch()
, kerangka kerja menyediakan tumpukan panggilan logis lengkap yang mencakup panggilan asinkron dan jarak jauh. Hal ini dapat membantu ketika debugging, terutama jika Anda memiliki metode asinkron memanggil metode asinkron lainnya. Sebagai contoh, pengecualian dari downloadImage akan menghasilkan pengecualian seperti ini:
RuntimeException: error downloading image at downloadImage(Main.java:35) at ---continuation---.(repeated:1) at errorHandlingAsync$1.doTry(Main.java:24) at ---continuation---.(repeated:1) …
TryCatchFinally Semantics
Eksekusi dari sebuah AWS Flow Framework untuk program Java dapat divisualisasikan sebagai pohon cabang yang dieksekusi secara bersamaan. Panggilan ke metode asinkron, aktivitas, dan TryCatchFinally
menciptakan sebuah cabang baru di pohon eksekusi ini. Misalnya, alur kerja pemrosesan citra dapat dilihat sebagai pohon yang ditunjukkan pada gambar berikut.
Kesalahan dalam satu cabang eksekusi akan menyebabkan pelepasan cabang itu, hanya sebagai pengecualian yang menyebabkan pelepasan tumpukan panggilan dalam program Java. Pelepasan terus bergerak ke atas cabang eksekusi sampai baik kesalahan ditangani atau akar pohon tercapai, dalam hal eksekusi alur kerja yang diakhiri.
Kerangka kerja melaporkan kesalahan yang terjadi saat memproses tugas sebagai pengecualian. Alur kerja Ini mengaitkan handler pengecualian (metode doCatch()
) didefinisikan dalam TryCatchFinally
dengan semua tugas yang dibuat oleh kode yang sesuai doTry()
. Jika tugas gagal-misalnya, karena batas waktu atau pengecualian tidak tertangani - maka pengecualian yang sesuai akan dimunculkan dan doCatch()
akan dipanggil untuk menanganinya. Untuk mencapai hal ini, kerangka kerja bekerja bersama-sama dengan Amazon SWF untuk menyebarkan kesalahan jarak jauh dan membangkitkan mereka sebagai pengecualian dalam konteks pemanggil.
Membatalkan
Ketika pengecualian terjadi dalam kode sinkron, kontrol melompat langsung ke blok catch
, melewatkan lebih dari kode yang tersisa di blok try
. Sebagai contoh:
try { a(); b(); c(); } catch (Exception e) { e.printStackTrace(); }
Dalam kode ini, jika b()
melempar pengecualian, maka c()
tidak pernah dipanggil. Membandingkan itu dengan alur kerja:
new TryCatch() { @Override protected void doTry() throws Throwable { activityA(); activityB(); activityC(); } @Override protected void doCatch(Throwable e) throws Throwable { e.printStackTrace(); } };
Dalam hal ini, panggilan ke activityA
, activityB
, dan activityC
semua kembali berhasil dan menghasilkan penciptaan tiga tugas yang akan dieksekusi secara asinkron. Katakanlah di lain waktu bahwa tugas untuk activityB
menghasilkan kesalahan. Kesalahan ini dicatatkan di riwayat oleh Amazon SWF. Untuk menangani hal ini, kerangka kerja pertama akan mencoba untuk membatalkan semua tugas-tugas lain yang berasal dalam lingkup doTry()
yang sama; dalam hal ini, activityA
dan activityC
. Ketika semua tugas tersebut selesai (membatalkan, gagal, atau berhasil diselesaikan), metode doCatch()
yang cocok akan dipanggil untuk menangani kesalahan.
Berbeda dengan contoh sinkron, dimana c()
tidak pernah dieksekusi, activityC
dipanggil dan tugas dijadwalkan untuk eksekusi; oleh karena itu, kerangka kerja akan membuat upaya untuk membatalkannya, tetapi tidak ada jaminan bahwa itu akan dibatalkan. Pembatalan tidak dapat dijamin karena aktivitas mungkin telah selesai, dapat mengabaikan permintaan pembatalan, atau mungkin gagal karena kesalahan. Namun, kerangka kerja tidak memberikan jaminan bahwa doCatch()
disebut hanya setelah semua tugas dimulai dari yang sesuai doTry()
telah selesai. Hal ini juga menjamin bahwa doFinally()
disebut hanya setelah semua tugas dimulai dari doTry()
dan doCatch()
telah selesai. Jika, misalnya, kegiatan dalam contoh di atas tergantung satu sama lain, katakanlah activityB
tergantung pada activityA
dan activityC
pada activityB
, maka pembatalan activityC
akan segera karena tidak dijadwalkan di Amazon SWF sampai activityB
selesai:
new TryCatch() { @Override protected void doTry() throws Throwable { Promise<Void> a = activityA(); Promise<Void> b = activityB(a); activityC(b); } @Override protected void doCatch(Throwable e) throws Throwable { e.printStackTrace(); } };
Aktivitas Heartbeat
AWS Flow Framework untuk mekanisme pembatalan koperasi Java mengizinkan tugas aktivitas dalam penerbangan dibatalkan dengan baik. Ketika pembatalan dipicu, tugas yang diblokir atau menunggu untuk ditugaskan ke pekerja secara otomatis dibatalkan. Jika, bagaimanapun, tugas sudah ditugaskan ke pekerja, kerangka kerja akan meminta aktivitas untuk membatalkan. Implementasi aktivitas Anda harus secara eksplisit menangani permintaan pembatalan tersebut. Hal ini dilakukan dengan melaporkan heartbeat aktivitas Anda.
Pelaporan heartbeat mengizinkan pelaksanaan kegiatan untuk melaporkan kemajuan tugas kegiatan yang sedang berlangsung, yang berguna untuk pemantauan, dan memungkinkan pemeriksaan aktivitas untuk permintaan pembatalan. Metode recordActivityHeartbeat
akan melemparkan CancellationException
jika pembatalan telah diminta. Pelaksanaan kegiatan dapat menangkap pengecualian ini dan bertindak atas permintaan pembatalan, atau dapat mengabaikan permintaan dengan menelan pengecualian. Untuk menghormati permintaan pembatalan, aktivitas harus melakukan pembersihan yang diinginkan, jika ada, dan kemudian melempar kembali CancellationException
. Ketika pengecualian ini dilemparkan dari pelaksanaan aktivitas, kerangka kerja mencatat bahwa tugas aktivitas telah selesai dalam status dibatalkan.
Contoh berikut menunjukkan aktivitas yang mengunduh dan memproses citra. Ini heartbeat setelah memproses setiap citra, dan jika pembatalan diminta, itu membersihkan dan melempar kembali pengecualian untuk mengakui pembatalan.
@Override public void processImages(List<String> urls) { int imageCounter = 0; for (String url: urls) { imageCounter++; Image image = download(url); process(image); try { ActivityExecutionContext context = contextProvider.getActivityExecutionContext(); context.recordActivityHeartbeat(Integer.toString(imageCounter)); } catch(CancellationException ex) { cleanDownloadFolder(); throw ex; } } }
Melaporkan aktivitas heartbeat tidak diperlukan, tetapi disarankan jika aktivitas Anda berjalan lama atau mungkin melakukan operasi mahal yang ingin dibatalkan dalam kondisi kesalahan. Anda harus menelepon heartbeatActivityTask
secara berkala dari pelaksanaan aktivitas.
Jika aktivitas habis, ActivityTaskTimedOutException
akan dilempar dan getDetails
pada objek pengecualian akan mengembalikan data yang diteruskan ke panggilan sukses terakhir ke heartbeatActivityTask
untuk tugas aktivitas yang sesuai. Implementasi alur kerja dapat menggunakan informasi ini untuk menentukan berapa banyak kemajuan yang dibuat sebelum tugas aktivitas habis.
catatan
Ini bukan praktik yang baik untuk heartbeat terlalu sering karena Amazon SWF mungkin throttle permintaan heartbeat. Lihat Panduan Developer Amazon Simple Workflow Service untuk batas yang ditempatkan oleh Amazon SWF.
Secara eksplisit Membatalkan tugas
Selain kondisi kesalahan, ada kasus lain di mana Anda dapat secara eksplisit membatalkan tugas. Misalnya, aktivitas untuk memproses pembayaran menggunakan kartu kredit mungkin perlu dibatalkan jika pengguna membatalkan pesanan. Kerangka kerja ini mengizinkan Anda untuk secara eksplisit membatalkan tugas yang dibuat dalam lingkup TryCatchFinally
. Pada contoh berikut, tugas pembayaran dibatalkan jika sinyal diterima saat pembayaran sedang diproses.
public class OrderProcessorImpl implements OrderProcessor { private PaymentProcessorClientFactory factory = new PaymentProcessorClientFactoryImpl(); boolean processingPayment = false; private TryCatchFinally paymentTask = null; @Override public void processOrder(int orderId, final float amount) { paymentTask = new TryCatchFinally() { @Override protected void doTry() throws Throwable { processingPayment = true; PaymentProcessorClient paymentClient = factory.getClient(); paymentClient.processPayment(amount); } @Override protected void doCatch(Throwable e) throws Throwable { if (e instanceof CancellationException) { paymentClient.log("Payment canceled."); } else { throw e; } } @Override protected void doFinally() throws Throwable { processingPayment = false; } }; } @Override public void cancelPayment() { if (processingPayment) { paymentTask.cancel(null); } } }
Menerima Notifikasi Tugas Dibatalkan
Ketika tugas selesai dalam keadaan dibatalkan, kerangka kerja menginformasikan logika alur kerja dengan melemparkan CancellationException
. Ketika suatu aktivitas selesai dalam keadaan dibatalkan, catatan dibuat dalam riwayat dan kerangka kerja panggilan yang sesuai doCatch()
dengan CancellationException
. Seperti yang ditunjukkan dalam contoh sebelumnya, ketika pembayaran proses tugas dibatalkan, alur kerja menerima CancellationException
.
Sebuah CancellationException
tidak tertangani disebarkan cabang eksekusi seperti exception lainnya. Namun, metode doCatch()
akan menerima CancellationException
jika tidak ada pengecualian lain dalam ruang lingkup; pengecualian lain diprioritaskan lebih tinggi dari pembatalan.
Nested TryCatchFinally
Anda dapat mengumpulkan TryCatchFinally
untuk menyesuaikan dengan kebutuhan Anda. Karena setiap TryCatchFinally
menciptakan cabang baru di pohon eksekusi, Anda dapat membuat cakupan bersarang. Exception dalam lingkup induk akan menyebabkan upaya pembatalan dari semua tugas yang diprakarsai oleh TryCatchFinally
bersarang ada di dalamnya. Namun, pengecualian dalam TryCatchFinally
bersarang tidak secara otomatis menyebarkan ke induk. Jika Anda ingin menyebarkan pengecualian dari TryCatchFinally
bersarang ke yang mengandung TryCatchFinally
, Anda harus membuang kembali pengecualian di doCatch()
. Dengan kata lain, hanya pengecualian tidak tertangani yang menggelegak, seperti try
/catch
Java. Jika Anda membatalkan TryCatchFinally
bersarang dengan memanggil metode pembatalan, TryCatchFinally
bersarang akan dibatalkan tetapi yang mengandung TryCatchFinally
tidak akan secara otomatis dibatalkan.
new TryCatch() { @Override protected void doTry() throws Throwable { activityA(); new TryCatch() { @Override protected void doTry() throws Throwable { activityB(); } @Override protected void doCatch(Throwable e) throws Throwable { reportError(e); } }; activityC(); } @Override protected void doCatch(Throwable e) throws Throwable { reportError(e); } };