远程数据的异步可迭代

时间:2015-06-09 16:36:38

标签: scala future scala-collections

我从远程API中提取了一些数据,我使用了Future风格的界面。数据结构为链表。相关示例数据容器如下所示。

case class Data(information: Int) {
    def hasNext: Boolean = ??? // Implemented
    def next: Future[Data] = ??? // Implemented
}

现在我有兴趣为数据类添加一些功能,例如mapforeachreduce等。为此,我想实现某种形式的{ {1}}这样就会影响这些方法。 下面给出的是特征IterableLike可以扩展,以便获得此属性。

Data

它应该是一个非阻塞实现,在执行操作时,开始从远程数据源请求下一个数据。 然后可以做一些很酷的东西,比如

trait AsyncIterable[+T]
    extends IterableLike[Future[T], AsyncIterable[T]]
{
    def hasNext : Boolean
    def next : Future[T]

    // How to implement?
    override def iterator: Iterator[Future[T]] = ???
    override protected[this] def newBuilder: mutable.Builder[Future[T], AsyncIterable[T]] = ???
    override def seq: TraversableOnce[Future[T]] = ???
}

接口不同也是可以接受的。但结果应该以某种方式表示集合上的异步迭代。优选地以开发人员熟悉的方式,因为它将是(开源)库的一部分。

2 个答案:

答案 0 :(得分:1)

在制作中,我会使用以下其中一种:

  1. Akka Streams
  2. Reactive Extensions
  3. 对于私人测试,我会实现类似于以下的内容。 (解释如下)

    我稍微修改了你的Data

    abstract class AsyncIterator[T] extends Iterator[Future[T]] {
      def hasNext: Boolean
      def next(): Future[T]
    }
    

    为此,我们可以实现此Iterable

    class AsyncIterable[T](sourceIterator: AsyncIterator[T])
      extends IterableLike[Future[T], AsyncIterable[T]]
    {
      private def stream(): Stream[Future[T]] =
        if(sourceIterator.hasNext) {sourceIterator.next #:: stream()} else {Stream.empty}
      val asStream = stream()
    
      override def iterator = asStream.iterator
      override def seq = asStream.seq
      override protected[this] def newBuilder = throw new UnsupportedOperationException()
    }
    

    如果使用以下代码查看它的实际效果:

    object Example extends App {
      val source = "Hello World!";
    
      val iterator1 = new DelayedIterator[Char](100L, source.toCharArray)
      new AsyncIterable(iterator1).foreach(_.foreach(print)) //prints 1 char per 100 ms
      pause(2000L)
    
      val iterator2 = new DelayedIterator[String](100L, source.toCharArray.map(_.toString))
      new AsyncIterable(iterator2).reduceLeft((fl: Future[String], fr) =>
        for(l <- fl; r <- fr) yield {println(s"$l+$r"); l + r}) //prints 1 line per 100 ms
      pause(2000L)
    
      def pause(duration: Long) = {println("->"); Thread.sleep(duration); println("\n<-")}
    }
    
    class DelayedIterator[T](delay: Long, data: Seq[T]) extends AsyncIterator[T] {
      private val dataIterator = data.iterator
      private var nextTime = System.currentTimeMillis() + delay
      override def hasNext = dataIterator.hasNext
      override def next = {
        val thisTime = math.max(System.currentTimeMillis(), nextTime)
        val thisValue = dataIterator.next()
        nextTime = thisTime + delay
        Future {
          val now = System.currentTimeMillis()
          if(thisTime > now) Thread.sleep(thisTime - now) //Your implementation will be better
          thisValue
        }
      }
    }
    

    解释

    AsyncIterable使用Stream,因为它计算得很懒,而且很简单。

    优点:

    • 简单
    • iteratorseq方法的多次调用会返回与所有项目相同的迭代次数。

    缺点:

    • 可能导致内存溢出,因为stream会保留所有主要获得的值。
    • 在创建AsyncIterable
    • 期间急切地获得第一个值

    DelayedIterator是非常简单的AsyncIterator实现,不要责怪我这里的快速和脏代码。

    我看到同步hasNext和异步next()

    仍然很奇怪

答案 1 :(得分:0)

使用Twitter Spool我已经实现了一个工作示例。 要实施spool,我修改了documentation

中的示例
import com.twitter.concurrent.Spool
import com.twitter.util.{Await, Return, Promise}

import scala.concurrent.{ExecutionContext, Future}

trait AsyncIterable[+T <: AsyncIterable[T]] { self : T =>
    def hasNext : Boolean
    def next : Future[T]

    def spool(implicit ec: ExecutionContext) : Spool[T] = {
        def fill(currentPage: Future[T], rest: Promise[Spool[T]]) {
            currentPage foreach { cPage =>
                if(hasNext) {
                    val nextSpool = new Promise[Spool[T]]
                    rest() = Return(cPage *:: nextSpool)
                    fill(next, nextSpool)
                } else {
                    val emptySpool = new Promise[Spool[T]]
                    emptySpool() = Return(Spool.empty[T])
                    rest() = Return(cPage *:: emptySpool)
                }
            }
        }
        val rest = new Promise[Spool[T]]
        if(hasNext) {
            fill(next, rest)
        } else {
            rest() = Return(Spool.empty[T])
        }
        self *:: rest
    }
}

数据与以前相同,现在我们可以使用它。

// Cool stuff
implicit val ec = scala.concurrent.ExecutionContext.global
val data = Data(1) // And others
// Print all the information asynchronously
val fut = data.spool.foreach(data => println(data.information))
Await.ready(fut)

它会在第二个元素上抛出异常,因为没有提供next的实现。