تحسين allOf و anyOf في CompleteableFuture



مرحبا مرة اخرى تحسبا لبداية الدورة التدريبية ، قام "Java Developer" بإعداد ترجمة لمواد مفيدة لك.


هناك طريقتان في CompletableFuture الذي يفاجئني التصميم بهما:

  • CompleteableFuture # allOf
  • CompleteableFuture # anyOf


في هذه المقالة سوف نرى ما هو الخطأ معهم وكيف يمكن جعلهم أكثر ملاءمة.

CompleteableFuture # allOf


لنلقِ نظرة على طريقة التوقيع:

 public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { // ... } 


هناك على الأقل مشكلتين مثيرتين للجدل هنا:

  1. يقبل الأسلوب العديد من كائنات CompletableFuture التي ترجع كائنات من أنواع مختلفة.
  2. تقوم الطريقة بإرجاع CompletableFuture ، والتي تقوم بإرجاع Void


أيضًا ، قد لا يحب البعض العدد المتغير للمعلمات ، لذلك دعونا ننظر إلى هذا الجزء أيضًا.

غالبًا ما يستخدم CompletableFuture<Void> كإشارة لإكمال العملية ؛ ومع ذلك ، من خلال إجراء تغيير بسيط في API ، يمكن استخدام هذه الطريقة كجهاز إشارة وكحامل لنتائج جميع العمليات المكتملة . دعنا نحاول القيام بذلك.

CompleteableFuture غير متزامن # allOf


أولاً ، دعنا نأتي بالتوقيع الصحيح.

من العدل أن نفترض أنه في معظم الحالات ، ستتم معالجة قائمة من CompletableFuture متجانسة وإرجاع CompletableFuture تحتوي على قائمة بالنتائج:

 public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { // ... } 


غالبًا ما تكون الأجزاء الداخلية للطريقة الأصلية أكثر تعقيدًا مما تتوقع:

 static CompletableFuture<Void> andTree( CompletableFuture<?>[] cfs, int lo, int hi) { CompletableFuture<Void> d = new CompletableFuture<Void>(); if (lo > hi) // empty d.result = NIL; else { CompletableFuture<?> a, b; int mid = (lo + hi) >>> 1; if ((a = (lo == mid ? cfs[lo] : andTree(cfs, lo, mid))) == null || (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] : andTree(cfs, mid+1, hi))) == null) throw new NullPointerException(); if (!d.biRelay(a, b)) { BiRelay<?,?> c = new BiRelay<>(d, a, b); a.bipush(b, c); c.tryFire(SYNC); } } return d; } 


لذلك ، بدلاً من إنشائه من نقطة الصفر ، سنحاول إعادة استخدام ما هو موجود بالفعل في الطريقة الأصلية كما لو كان من المفترض استخدامه كجهاز للإشارة إلى الإكمال ... ثم قم بتغيير النتيجة الفراغية إلى القائمة المستقبلية:

 CompletableFuture<List<CompletableFuture<T>>> i = futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l))); 


جيد حتى الان تمكنا من الحصول عليها
CompletableFuture<List<CompletableFuture<T> > > CompletableFuture<Void> بدلاً من CompletableFuture<Void> ، وهو أمر جيد بالفعل. لكننا لسنا بحاجة إلى قائمة المستقبل مع النتائج ، نحن بحاجة إلى قائمة النتائج.

الآن يمكننا ببساطة معالجة القائمة وإزالة المستقبل غير المرغوب فيه منها. من الطبيعي تمامًا استدعاء أساليب الانضمام إلى CompleteableFuture # ، لأننا نعلم أنه لن يتم حظرها أبدًا (في هذه المرحلة ، قد اكتمل كل المستقبل بالفعل):

 CompletableFuture<List<T>> result = intermediate .thenApply(list -> list.stream() .map(CompletableFuture::join) .collect(toList())); 


الآن دعنا نجمع كل هذا في حل نهائي:

 public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { return futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l.stream() .map(CompletableFuture::join) .collect(Collectors.toList())))); } 


غير متزامن وسقوط CompleteableFuture # allOf


إذا كانت هناك استثناءات ، فإن CompleteableFuture # allOf الأصلي ينتظر أن تكتمل جميع العمليات المتبقية.

وإذا أردنا الإبلاغ عن اكتمال العملية عند حدوث استثناء ، فسيتعين علينا تغيير التطبيق.

للقيام بذلك ، قم بإنشاء مثيل جديد لـ CompletableFuture وإنهائه يدويًا بعد قيام إحدى العمليات برفع استثناء:

 CompletableFuture<List<T>> result = new CompletableFuture<>(); futures.forEach(f -> f .handle((__, ex) -> ex == null || result.completeExceptionally(ex))); 


... ولكن بعد ذلك نحتاج إلى التعامل مع السيناريو عندما يتم إكمال كل المستقبل بنجاح. يمكن القيام بذلك بسهولة باستخدام طريقة allOf () المحسنة ، ثم ببساطة إنهاء المستقبل يدويًا:

 allOf(futures).thenAccept(result::complete); 


الآن يمكننا دمج كل شيء معًا لتشكيل الحل النهائي:

 public static <T> CompletableFuture<List<T>> allOfShortcircuiting(Collection<CompletableFuture<T>> futures) { CompletableFuture<List<T>> result = new CompletableFuture<>(); for (CompletableFuture<?> f : futures) { f.handle((__, ex) -> ex == null || result.completeExceptionally(ex)); } allOf(futures).thenAccept(result::complete); return result; } 


CompleteableFuture # anyOf


لنبدأ بتوقيع الطريقة:

 public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) { // ... } 


يمكننا على الفور اكتشاف نفس المشكلات كما هو الحال مع الطريقة المذكورة أعلاه:

  1. يقبل الأسلوب العديد من كائنات CompletableFuture التي تحتوي على كائنات من أنواع مختلفة.
  2. إرجاع الأسلوب CompletableFuture يحتوي على كائن من النوع Object .


بقدر ما أفهم ، تم تصميم CompletableFuture#allOf ليتم استخدامه كجهاز إشارة. لكن CompletableFuture#anyOf لا تتبع هذه الفلسفة ، حيث تقوم بإرجاع CompletableFuture<Object> ، وهو أمر مربك أكثر.

ألقِ نظرة على المثال التالي حيث أحاول معالجة CompleteableFuture التي تحتوي على بيانات من أنواع مختلفة:

 CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(1); CompletableFuture<String> f2 = CompletableFuture.completedFuture("2"); Integer result = CompletableFuture.anyOf(f1, f2) .thenApply(r -> { if (r instanceof Integer) { return (Integer) r; } else if (r instanceof String) { return Integer.valueOf((String) r); } throw new IllegalStateException("unexpected object type!"); }).join(); 


غير مريح للغاية ، أليس كذلك؟

لحسن الحظ ، هذا سهل إلى حد ما للتكيف مع سيناريو أكثر شيوعا (في انتظار واحدة من العديد من القيم التي تحتوي على المستقبل من نفس النوع) عن طريق تغيير التوقيع وإدخال صب النوع المباشر.

وبالتالي ، من خلال التحسينات التي أجريناها ، يمكننا إعادة استخدام الأساليب الحالية وتحقيق النتيجة بأمان:

 public static <T> CompletableFuture<T> anyOf(List<CompletableFuture<T>> cfs) { return CompletableFuture.anyOf(cfs.toArray(new CompletableFuture[0])) .thenApply(o -> (T) o); } public static <T> CompletableFuture<T> anyOf(CompletableFuture<T>... cfs) { return CompletableFuture.anyOf(cfs).thenApply(o -> (T) o); } 


شفرة المصدر


شفرة المصدر يمكن العثور عليها على جيثب .

هذا كل شيء. أراك في الدورة .

Source: https://habr.com/ru/post/ar481804/


All Articles