Birkaç Vadeli İşlem İçin Nasıl Beklenir?


86

Birkaç geleceklerini ve beklemek gerek olduğunu varsayalım ya bunlardan herhangi başarısız veya hepsini başarılı.

Örneğin: Let 3 futures vardır: f1, f2, f3.

  • Eğer f1başarılı ve f2başarısız ben beklemeyin f3(ve dönüş yetmezliği müşteriye).

  • Eğer devam f2ederken başarısız olursa f1ve f3hala çalışıyorsa, onları beklemiyorum (ve hata veriyorum )

  • Başarılı f1olur ve sonra f2başarılı olursa beklemeye devam ederim f3.

Nasıl uygularsın?


Bu soru ile ilgili bir Scala sorunu. konular.scala-lang.org/browse/SI-8994 API, farklı davranışlar için bir seçeneğe sahip olmalıdır
WeiChing 林 煒 清

Yanıtlar:


83

Bunun yerine aşağıdaki gibi bir anlama için kullanabilirsiniz:

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

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

Bu örnekte 1, 2 ve 3 vadeli işlemler paralel olarak başlatılır. Daha sonra, anlamak için, 1 ve ardından 2 ve ardından 3 sonuçlarının çıkmasını bekleriz. 1 veya 2 başarısız olursa, artık 3'ü beklemeyeceğiz. 3'ü de başarılı olursa, aggFutval 3 vadeli işlemin sonuçlarına karşılık gelen 3 yuvalı bir tuple tutacaktır.

Şimdi, önce fut2 başarısız olursa, beklemeyi bırakmak istediğiniz davranışa ihtiyacınız varsa, işler biraz daha karmaşık hale gelir. Yukarıdaki örnekte, fut2'nin başarısız olduğunu fark etmeden önce fut1'in tamamlanmasını beklemeniz gerekir. Bunu çözmek için şuna benzer bir şey deneyebilirsiniz:

  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)
  }

Şimdi bu doğru çalışıyor, ancak sorun, başarıyla tamamlandığında hangisinin Futurekaldırılacağını bilmekten kaynaklanıyor Map. Bir sonucu ortaya çıkaran Gelecek ile bir sonucu doğru bir şekilde ilişkilendirmek için bir yolunuz olduğu sürece, bunun gibi bir şey çalışır. Tamamlanan Vadeli İşlemleri tekrar tekrar Haritadan kaldırmaya devam eder ve ardından Future.firstCompletedOfkalan Futureskısmı kalmayana kadar çağırarak yol boyunca sonuçları toplar. Hoş değil, ama gerçekten bahsettiğiniz davranışa ihtiyacınız varsa, o zaman bu veya benzeri bir şey işe yarayabilir.


Teşekkür ederim. Daha fut2önce başarısız olursa ne olur fut1? fut1Bu durumda yine de bekleyecek miyiz ? İstersek tam olarak istediğim bu değil.
Michael

Ama önce 3 başarısız olursa, yine de erken dönebileceğimizde 1 ve 2'yi bekleriz. Bunu gelecekleri sıralamaya gerek kalmadan yapmanın bir yolu var mı?
Arketipsel Paul

Bir yükleyebilirsiniz onFailureiçin işleyici fut2hızlı başarısız ve onSuccessüzerinde aggFuttanıtıcı başarının. İma edilen aggFutifadelerdeki başarı fut2başarılı bir şekilde tamamlandı, bu nedenle işleyicilerden yalnızca birini aradınız.
pagoda_5b

Gelecekten herhangi birinin başarısız olması durumunda hızlı başarısızlık için olası bir çözümü göstermek için cevabıma biraz daha ekledim.
cmbaxter

1
İlk örneğinizde, 1 2 ve 3 paralel olarak çalışmaz, ardından seri olarak çalışır. Baskı çizgileriyle deneyin ve görün
bwawok

35

Bir söz kullanabilir ve ona ilk başarısızlığı veya nihai tamamlanmış toplu başarıyı gönderebilirsiniz:

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
}

O zaman, bunu engellemek istiyorsanız veya sadece başka bir şeye dönüştürmek istiyorsanız Awaitsonuç alabilirsiniz .Futuremap

Anlamak için olan fark, burada ilk başarısız olanın hatasını alırken, kavrama için girdi koleksiyonunun geçiş sırasındaki ilk hatayı alırsınız (ilk önce başka biri başarısız olsa bile). Örneğin:

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

Ve:

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

İşte oyuncu kullanmadan bir çözüm.

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

Bunu sadece futures ile yapabilirsiniz. İşte bir uygulama. Yürütmeyi erken sonlandırmayacağını unutmayın! Bu durumda daha karmaşık bir şey yapmanız gerekir (ve muhtemelen kesintiyi kendiniz uygulamanız gerekir). Ancak işe yaramayacak bir şeyi beklemeye devam etmek istemiyorsanız, anahtar ilk şeyin bitmesini beklemeye devam etmek ve ya hiçbir şey kalmadığında ya da bir istisnaya bastığınızda durmaktır:

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 )
      }
  }
}

İşte her şeyin yolunda gittiği bir uygulama örneği:

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((), (), ()))

Ama bir şeyler ters gittiğinde:

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
Güzel uygulama. Ancak unutmayın ki, eğer boş bir vadeli işlemler dizisini geçerseniz, Başarıyı sonsuza kadar bekler ...
Michael Rueegg

5

Bu amaçla bir Akka oyuncusu kullanırım. Kavrayıştan farklı olarak, geleceklerden herhangi biri başarısız olur olmaz başarısız olur, bu nedenle bu anlamda biraz daha etkilidir.

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

Ardından, oyuncuyu yaratın, ona bir mesaj gönderin (böylece cevabını nereye göndereceğini bilsin) ve bir cevap bekleyin.

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
}

Böylesine basit bir görev için biraz fazla karmaşık görünüyor. Geleceği beklemek için gerçekten bir aktöre ihtiyacım var mı? Yine de teşekkürler.
Michael

1
API'de tam olarak istediğinizi yapabilecek uygun bir yöntem bulamadım ama belki bir şeyi kaçırmışımdır.
Robin Green

5

Bu soru yanıtlandı ancak burada bir tane olmadığı için değer sınıfı çözümümü (değer sınıfları 2.10'da eklendi) gönderiyorum. Lütfen eleştirmekten çekinmeyin.

  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)
  }

ConcurrentFuture, varsayılan Future map / flatMap'i bunu-sonra-yap yerine, başarısız olursa hepsini-ve-başarısız-birleştirmeye değiştiren ek yükü olmayan bir Gelecek sarmalayıcısıdır. Kullanım:

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") }

Yukarıdaki örnekte, f1, f2 ve f3 eşzamanlı olarak çalışacak ve herhangi bir sırada herhangi bir başarısızlık olursa, demetin geleceği anında başarısız olacaktır.


Harika! Bu tür bir fayda işlevi sağlayan herhangi bir kitaplık var mı?
srirachapills

1
Evet, o zamandan beri kapsamlı bir Future yardımcı programı lib oluşturdum: github.com/S-Mach/s_mach.concurrent Örnek kodda async.par'a bakın.
lancegatlin


2

Bunu kullanabilirsiniz:

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")
  }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.