النتائج المستقبلية والحصول عليها
النتائج المستقبلية والحصول عليها
عندما تُرسل مهمّة إلى ExecutorService، تعود الاستدعاءة فورًا — لكنّ المهمّة تواصل تنفيذها في الخلفية. الجسر بين تلك العملية الخلفية وشيفرتك هو Future<V>: مقبض على حساب لم ينته بعد. إتقان كيفية استرجاع النتائج وتحديد مهلها وإلغائها أمر لا غنى عنه لكتابة برامج متزامنة صحيحة.
ما هو Future؟
java.util.concurrent.Future<V> واجهة جنيسة تحتوي خمس توابع. المعامل النوعي V هو نوع النتيجة عند اكتمال الحساب.
يُعيد submit(Callable<V>) كائنًا من نوع Future<V>. تعمل Callable المُرسَلة على خيط التجمّع، بينما يحجب future.get() الخيط المُستدعي حتى تتوفّر النتيجة.
التوابع الخمسة لـ Future
get()— يحجب إلى الأبد حتى الاكتمال ثم يُعيد النتيجة.get(long timeout, TimeUnit unit)— يحجب لمدة زمنية محدّدة؛ يُطلقTimeoutExceptionعند انتهاء المهلة.cancel(boolean mayInterruptIfRunning)— يحاول الإلغاء؛ يُعيدfalseإن كانت المهمّة انتهت بالفعل أو يتعذّر إلغاؤها.isCancelled()— يُعيدtrueإن أُلغيت قبل اكتمالها.isDone()— يُعيدtrueإن انتهت (طبيعيًا أو بالإلغاء أو باستثناء).
الحجب مع مهلة زمنية
الحجب إلى الأبد بـ get() خطأ شبه مؤكّد في التطبيقات الحقيقية — إذ قد تُعطّل مهمّة واحدة متوقّفة خيطك كلّه إلى الأبد. الأجدر الاستخدام النسخة ذات المهلة:
Callable أي استثناء محدّد أو غير محدّد، يُغلّفه get() داخل ExecutionException. تجاهُله يعني ابتلاع الأخطاء في صمت. استدعِ دائمًا e.getCause() للحصول على المشكلة الحقيقية.
الإلغاء بالتفصيل
القيمة المنطقية المُمرَّرة لـ cancel() ذات أهمية:
cancel(false)— إذا لم تبدأ المهمّة بعد، يمنعها من البدء؛ وإن كانت تعمل بالفعل، يتركها تُكمل.cancel(true)— علاوة على ذلك، يُرسل إشارة إيقاف للخيط الذي ينفّذ المهمّة.
الإيقاف يعمل فقط إذا تعاون الكود المُشغَّل — يجب أن يستدعي تابعًا حاجبًا يُطلق InterruptedException، أو يتحقّق دوريًا من Thread.currentThread().isInterrupted().
InterruptedException أو استعِد الراية عند اصطيادها. المهام التي تتجاهل الإيقاف لا يمكن إلغاؤها بنظافة.
الاستثناءات المحدّدة من get()
يُعلن get() ثلاثة استثناءات محدّدة يجب معالجتها:
- InterruptedException — أُوقف الخيط المنتظر نفسه. استعِد الراية دائمًا:
Thread.currentThread().interrupt(). - ExecutionException — أطلقت المهمّة استثناءً. افكّه بـ
getCause(). - TimeoutException (نسخة المهلة فقط) — انقضت المهلة قبل وصول النتيجة.
FutureTask: الجمع بين Runnable وFuture
تُنفّذ FutureTask<V> كلًّا من Runnable وFuture<V>. يمكنك تغليف Callable فيها وإرسالها للمنفّذ مع الاحتفاظ بمرجع Future مكتوب النوع — مفيد حين تحتاج لتمرير كائن المهمّة قبل إرساله:
الاستطلاع مقابل الحجب
يمكن تجنّب الحجب كليًّا إن كان لديك عمل آخر أثناء الانتظار:
تعمل المهمّتان بالتوازي. لا تحجب إلا حين تحتاج النتائج فعلًا، لا عند إرسالها.
CompletableFuture في Java 8. الدرسان التاليان يتناولانه بالتفصيل.
الخلاصة
يمنحك Future<V> مقبضًا على عملية حسابية غير متزامنة. استخدم نسخة المهلة من get() في كود الإنتاج، وتعامل دائمًا مع ExecutionException بفحص سببه، واستدعِ cancel(true) لإلغاء المهام المتفلّتة — لكن فقط إن كانت مهمّتك تتعاون مع الإيقاف. لتركيب أكثر ثراءً، الخطوة التالية هي CompletableFuture.