कई फ्यूचर्स का इंतजार कैसे करें?


86

मान लीजिए मैं कई वायदा और तक इंतजार करना की जरूरत है या तो उनमें से किसी में विफल रहता है या उन सभी को सफल होते हैं।

उदाहरण के लिए: Let वहाँ 3 वायदा कर रहे हैं: f1, f2, f3

  • यदि f1सफल होता है और f2विफल रहता है तो मैं इंतजार नहीं करता f3(और क्लाइंट को विफलता लौटाता हूं )।

  • यदि f2विफल रहता है f1और f3अभी भी चल रहा है तो मैं उनके लिए इंतजार नहीं करता (और वापसी विफलता )

  • अगर f1सफल होता है और फिर f2सफल होता है तो मैं इंतजार करना जारी रखता हूं f3

आप इसे कैसे लागू करेंगे?


इस प्रश्न के बारे में एक स्काला मुद्दा। मुद्दों .scala-lang.org/browse/SI-8994 एपीआई में विभिन्न व्यवहारों के लिए विकल्प होना चाहिए
वीचिंग C hing '

जवाबों:


83

आप इसके बजाय निम्न के लिए एक समझ का उपयोग कर सकते हैं:

val fut1 = Future{...}
val fut2 = Future{...}
val fut3 = Future{...}

val aggFut = for{
  f1Result <- fut1
  f2Result <- fut2
  f3Result <- fut3
} yield (f1Result, f2Result, f3Result)

इस उदाहरण में, वायदा 1, 2 और 3 को समानांतर में बंद किया गया है। फिर, समझ के लिए, हम 1 और फिर 2 और फिर 3 उपलब्ध होने तक इंतजार करते हैं। यदि 1 या 2 विफल रहता है, तो हम 3 के लिए और इंतजार नहीं करेंगे। यदि सभी 3 सफल होते हैं, तो aggFutवैल 3 फ्यूचर्स के परिणामों के अनुरूप 3 स्लॉट्स के साथ एक टपल पकड़ लेगा।

अब अगर आपको उस व्यवहार की ज़रूरत है जहाँ आप रुकना चाहते हैं अगर कहते हैं कि fut2 पहले विफल हो जाता है, तो चीजें थोड़ी पेचीदा हो जाती हैं। उपरोक्त उदाहरण में, आपको fut2 के विफल होने से पहले पूर्ण करने के लिए fut1 का इंतजार करना होगा। इसे हल करने के लिए, आप कुछ इस तरह की कोशिश कर सकते हैं:

  val fut1 = Future{Thread.sleep(3000);1}
  val fut2 = Promise.failed(new RuntimeException("boo")).future
  val fut3 = Future{Thread.sleep(1000);3}

  def processFutures(futures:Map[Int,Future[Int]], values:List[Any], prom:Promise[List[Any]]):Future[List[Any]] = {
    val fut = if (futures.size == 1) futures.head._2
    else Future.firstCompletedOf(futures.values)

    fut onComplete{
      case Success(value) if (futures.size == 1)=> 
        prom.success(value :: values)

      case Success(value) =>
        processFutures(futures - value, value :: values, prom)

      case Failure(ex) => prom.failure(ex)
    }
    prom.future
  }

  val aggFut = processFutures(Map(1 -> fut1, 2 -> fut2, 3 -> fut3), List(), Promise[List[Any]]())
  aggFut onComplete{
    case value => println(value)
  }

अब यह सही ढंग से काम करता है, लेकिन यह मुद्दा यह जानने से आता है कि कौन Futureसे Mapको सफलतापूर्वक पूरा किया जाए। जब तक आपके पास भविष्य के साथ परिणाम को ठीक से सहसंबंधित करने का कोई रास्ता होता है, तब तक यह परिणाम सामने आ जाता है, तब कुछ इस तरह से काम करता है। यह केवल पुनरावर्ती रूप से पूर्ण किए गए फ़्यूचर्स को मैप से निकालता रहता है और तब तक Future.firstCompletedOfशेष पर कॉल करता रहता है Futuresजब तक कि कोई भी नहीं बचा है, परिणाम को रास्ते में एकत्रित करता है। यह सुंदर नहीं है, लेकिन अगर आपको वास्तव में उस व्यवहार की आवश्यकता है जिसके बारे में आप बात कर रहे हैं, तो यह, या ऐसा ही कुछ काम कर सकता है।


धन्यवाद। यदि fut2पहले विफल हो जाता है तो क्या होता है fut1? क्या हम अब भी fut1उस मामले में इंतजार करेंगे ? अगर हम यह नहीं चाहते हैं कि मैं क्या चाहता हूं।
माइकल

लेकिन अगर 3 पहले विफल हो जाते हैं, तो हम अभी भी 1 और 2 की प्रतीक्षा करते हैं जब हम जल्दी लौट सकते थे। वायदा अनुक्रमण की आवश्यकता के बिना ऐसा करने का कोई तरीका?
अर्चेतपाल पॉल

आप एक स्थापित कर सकते हैं onFailureके लिए हैंडलर fut2तेजी से विफल है, और एक onSuccessपर aggFutसंभाल सफलता के लिए। aggFutतात्पर्य है fut2कि सफलताओं पर सफलता पूर्ण रूप से मिलती है, इसलिए आपके पास केवल एक हैंडलर है।
शिवालय_5

मैंने अपने उत्तर में थोड़ा और तेजी से जुड़ने के लिए एक संभावित समाधान दिखाया अगर कोई भी वायदा विफल रहता है।
caxaxter

1
आपके पहले उदाहरण में, 1 2 और 3 समानांतर में नहीं चलते हैं, फिर धारावाहिक में चलते हैं। इसे प्रिंटलाइन के साथ आज़माएं और देखें
bwawok

35

आप एक वादा का उपयोग कर सकते हैं, और इसे पहली असफलता, या अंतिम पूर्ण एकत्रित सफलता के लिए भेज सकते हैं:

def sequenceOrBailOut[A, M[_] <: TraversableOnce[_]](in: M[Future[A]] with TraversableOnce[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = {
  val p = Promise[M[A]]()

  // the first Future to fail completes the promise
  in.foreach(_.onFailure{case i => p.tryFailure(i)})

  // if the whole sequence succeeds (i.e. no failures)
  // then the promise is completed with the aggregated success
  Future.sequence(in).foreach(p trySuccess _)

  p.future
}

तब आप उस Awaitपर परिणाम कर सकते हैं Futureयदि आप ब्लॉक करना चाहते हैं, या बस mapइसे किसी और चीज में डालना चाहते हैं।

समझ के साथ अंतर यह है कि यहां आपको पहली बार असफल होने की त्रुटि मिलती है, जबकि समझ के साथ आपको इनपुट संग्रह के ट्रैवर्सल ऑर्डर में पहली त्रुटि मिलती है (भले ही कोई दूसरा पहले विफल हो गया हो)। उदाहरण के लिए:

val f1 = Future { Thread.sleep(1000) ; 5 / 0 }
val f2 = Future { 5 }
val f3 = Future { None.get }

Future.sequence(List(f1,f2,f3)).onFailure{case i => println(i)}
// this waits one second, then prints "java.lang.ArithmeticException: / by zero"
// the first to fail in traversal order

तथा:

val f1 = Future { Thread.sleep(1000) ; 5 / 0 }
val f2 = Future { 5 }
val f3 = Future { None.get }

sequenceOrBailOut(List(f1,f2,f3)).onFailure{case i => println(i)}
// this immediately prints "java.util.NoSuchElementException: None.get"
// the 'actual' first to fail (usually...)
// and it returns early (it does not wait 1 sec)

7

यहाँ अभिनेताओं का उपयोग किए बिना एक समाधान है।

import scala.util._
import scala.concurrent._
import java.util.concurrent.atomic.AtomicInteger

// Nondeterministic.
// If any failure, return it immediately, else return the final success.
def allSucceed[T](fs: Future[T]*): Future[T] = {
  val remaining = new AtomicInteger(fs.length)

  val p = promise[T]

  fs foreach {
    _ onComplete {
      case s @ Success(_) => {
        if (remaining.decrementAndGet() == 0) {
          // Arbitrarily return the final success
          p tryComplete s
        }
      }
      case f @ Failure(_) => {
        p tryComplete f
      }
    }
  }

  p.future
}

5

इसे आप अकेले फ्यूचर के साथ कर सकते हैं। यहाँ एक कार्यान्वयन है। ध्यान दें कि यह निष्पादन को जल्दी समाप्त नहीं करेगा! उस मामले में आपको कुछ और अधिक परिष्कृत करने की आवश्यकता है (और संभवतः व्यवधान को स्वयं लागू करें)। लेकिन अगर आप सिर्फ उस चीज का इंतजार नहीं करना चाहते हैं जो काम नहीं करने वाली है, तो सबसे पहली चीज खत्म होने का इंतजार करते रहना है, और जब कुछ भी न बचा हो तो रुक जाना चाहिए या आप एक अपवाद मार दें:

import scala.annotation.tailrec
import scala.util.{Try, Success, Failure}
import scala.concurrent._
import scala.concurrent.duration.Duration
import ExecutionContext.Implicits.global

@tailrec def awaitSuccess[A](fs: Seq[Future[A]], done: Seq[A] = Seq()): 
Either[Throwable, Seq[A]] = {
  val first = Future.firstCompletedOf(fs)
  Await.ready(first, Duration.Inf).value match {
    case None => awaitSuccess(fs, done)  // Shouldn't happen!
    case Some(Failure(e)) => Left(e)
    case Some(Success(_)) =>
      val (complete, running) = fs.partition(_.isCompleted)
      val answers = complete.flatMap(_.value)
      answers.find(_.isFailure) match {
        case Some(Failure(e)) => Left(e)
        case _ =>
          if (running.length > 0) awaitSuccess(running, answers.map(_.get) ++: done)
          else Right( answers.map(_.get) ++: done )
      }
  }
}

जब सब कुछ ठीक काम करता है तो इसका एक उदाहरण यहां दिया गया है:

scala> awaitSuccess(Seq(Future{ println("Hi!") }, 
  Future{ Thread.sleep(1000); println("Fancy meeting you here!") },
  Future{ Thread.sleep(2000); println("Bye!") }
))
Hi!
Fancy meeting you here!
Bye!
res1: Either[Throwable,Seq[Unit]] = Right(List((), (), ()))

लेकिन जब कुछ गलत होता है:

scala> awaitSuccess(Seq(Future{ println("Hi!") }, 
  Future{ Thread.sleep(1000); throw new Exception("boo"); () }, 
  Future{ Thread.sleep(2000); println("Bye!") }
))
Hi!
res2: Either[Throwable,Seq[Unit]] = Left(java.lang.Exception: boo)

scala> Bye!

1
अच्छा कार्यान्वयन। लेकिन ध्यान दें कि यदि आप वायदा करने के लिए वायदा का एक खाली क्रम पारित करते हैं, तो यह हमेशा के लिए इंतजार करता है ...
माइकल रूएग

5

इस उद्देश्य के लिए मैं अक्का अभिनेता का उपयोग करूंगा। फॉर-कॉम्प्रिहेंशन के विपरीत, यह किसी भी फ्यूचर के फेल होते ही फेल हो जाता है, इसलिए यह इस मायने में थोड़ा अधिक कुशल है।

class ResultCombiner(futs: Future[_]*) extends Actor {

  var origSender: ActorRef = null
  var futsRemaining: Set[Future[_]] = futs.toSet

  override def receive = {
    case () =>
      origSender = sender
      for(f <- futs)
        f.onComplete(result => self ! if(result.isSuccess) f else false)
    case false =>
      origSender ! SomethingFailed
    case f: Future[_] =>
      futsRemaining -= f
      if(futsRemaining.isEmpty) origSender ! EverythingSucceeded
  }

}

sealed trait Result
case object SomethingFailed extends Result
case object EverythingSucceeded extends Result

फिर, अभिनेता बनाएं, इसे एक संदेश भेजें (ताकि यह पता चल जाए कि कहां अपना जवाब भेजना है) और एक उत्तर की प्रतीक्षा करें।

val actor = actorSystem.actorOf(Props(new ResultCombiner(f1, f2, f3)))
try {
  val f4: Future[Result] = actor ? ()
  implicit val timeout = new Timeout(30 seconds) // or whatever
  Await.result(f4, timeout.duration).asInstanceOf[Result] match {
    case SomethingFailed => println("Oh noes!")
    case EverythingSucceeded => println("It all worked!")
  }
} finally {
  // Avoid memory leaks: destroy the actor
  actor ! PoisonPill
}

इस तरह के एक सरल कार्य के लिए थोड़ा बहुत जटिल लगता है। क्या मुझे वास्तव में एक अभिनेता की जरूरत है जो सिर्फ वायदा का इंतजार करे? फिर भी धन्यवाद।
माइकल

1
मुझे एपीआई में कोई उपयुक्त तरीका नहीं मिला, जो आप चाहते हैं वही कर सकते हैं, लेकिन शायद मैं कुछ याद कर रहा हूं।
रॉबिन ग्रीन

5

इस सवाल का जवाब दिया गया है, लेकिन मैं अपना मूल्य वर्ग समाधान पोस्ट कर रहा हूं (मूल्य वर्ग 2.10 में जोड़ा गया था) क्योंकि यहां कोई नहीं है। कृपया बेझिझक आलोचना करें।

  implicit class Sugar_PimpMyFuture[T](val self: Future[T]) extends AnyVal {
    def concurrently = ConcurrentFuture(self)
  }
  case class ConcurrentFuture[A](future: Future[A]) extends AnyVal {
    def map[B](f: Future[A] => Future[B]) : ConcurrentFuture[B] = ConcurrentFuture(f(future))
    def flatMap[B](f: Future[A] => ConcurrentFuture[B]) : ConcurrentFuture[B] = concurrentFutureFlatMap(this, f) // work around no nested class in value class
  }
  def concurrentFutureFlatMap[A,B](outer: ConcurrentFuture[A], f: Future[A] => ConcurrentFuture[B]) : ConcurrentFuture[B] = {
    val p = Promise[B]()
    val inner = f(outer.future)
    inner.future onFailure { case t => p.tryFailure(t) }
    outer.future onFailure { case t => p.tryFailure(t) }
    inner.future onSuccess { case b => p.trySuccess(b) }
    ConcurrentFuture(p.future)
  }

समवर्ती फ़ॉरेस्ट एक ओवरहेड फ़्यूचर रैपर है जो डिफ़ॉल्ट फ़्यूचर मैप / फ़्लैटफ़ॉर्म को डू-ए-फ़ेयर से बदल देता है, जो कि सभी-और-फ़ेल-इफ़-अगर-फ़ेल हो जाता है। उपयोग:

def func1 : Future[Int] = Future { println("f1!");throw new RuntimeException; 1 }
def func2 : Future[String] = Future { Thread.sleep(2000);println("f2!");"f2" }
def func3 : Future[Double] = Future { Thread.sleep(2000);println("f3!");42.0 }

val f : Future[(Int,String,Double)] = {
  for {
    f1 <- func1.concurrently
    f2 <- func2.concurrently
    f3 <- func3.concurrently
  } yield for {
   v1 <- f1
   v2 <- f2
   v3 <- f3
  } yield (v1,v2,v3)
}.future
f.onFailure { case t => println("future failed $t") }

ऊपर के उदाहरण में, f1, f2 और f3 समवर्ती रूप से चलेंगे और यदि किसी भी क्रम में कोई भी असफलता आती है तो टुप्ल का भविष्य तुरंत विफल हो जाएगा।


बहुत बढ़िया! कोई भी लिबास जो उस तरह का यूटिलिटी फंक्शन प्रदान करता है?
srirachapills

1
हाँ, मैंने तब से एक व्यापक भविष्य उपयोगिता उपयोगिता बनाई है: github.com/S-Mach/s_mach.concurrent उदाहरण कोड में async.par देखें।
लांसगेटलिन

5

आप ट्विटर के फ्यूचर एपीआई की जांच कर सकते हैं। विशेष रूप से Future.collect पद्धति। यह वही करता है जो आप चाहते हैं: https://twitter.github.io/scala_school/finagle.html

स्रोत कोड Future.scala यहां उपलब्ध है: https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala


2

आप इसका उपयोग कर सकते हैं:

val l = List(1, 6, 8)

val f = l.map{
  i => future {
    println("future " +i)
    Thread.sleep(i* 1000)
    if (i == 12)
      throw new Exception("6 is not legal.")
    i
  }
}

val f1 = Future.sequence(f)

f1 onSuccess{
  case l => {
    logInfo("onSuccess")
    l.foreach(i => {

      logInfo("h : " + i)

    })
  }
}

f1 onFailure{
  case l => {
    logInfo("onFailure")
  }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.