Java 在异步生成的元素流上使用hasNext()和next()进行迭代
我必须使用hasNext()和next()方法实现一个迭代器接口(由Java API定义),该接口应该返回源自异步处理的HTTP响应(使用Akka actors处理)的结果元素 必须满足以下要求:Java 在异步生成的元素流上使用hasNext()和next()进行迭代,java,scala,asynchronous,iterator,java-stream,Java,Scala,Asynchronous,Iterator,Java Stream,我必须使用hasNext()和next()方法实现一个迭代器接口(由Java API定义),该接口应该返回源自异步处理的HTTP响应(使用Akka actors处理)的结果元素 必须满足以下要求: 不要阻塞并等待异步操作完成,因为生成大型结果集可能需要一段时间(迭代器应在结果元素可用时立即返回结果元素) Iterator.next()应该阻塞,直到下一个元素可用为止(如果没有更多的元素,则抛出异常) Iterator.hasNext()应该返回true,只要还有更多元素(即使下一个元素还不可用
- 不要阻塞并等待异步操作完成,因为生成大型结果集可能需要一段时间(迭代器应在结果元素可用时立即返回结果元素)
- Iterator.next()应该阻塞,直到下一个元素可用为止(如果没有更多的元素,则抛出异常)
- Iterator.hasNext()应该返回true,只要还有更多元素(即使下一个元素还不可用)
- 结果的总数事先未知。结果生成参与者将在完成时发送特定的“结束消息”
- 尽量避免使用InterruptedException,例如,当迭代器正在等待一个空队列,但不会生成更多的元素时
类ResultStreamIterator扩展迭代器[Result]{
val resultQueue=new ArrayBlockingQueue[选项[结果]](100)
def hasNext():Boolean=???//如果尚未完成,则返回true
def next():结果=???//如果尚未完成,则获取()下一个元素
案例类结果(值:Any)//由结果生成参与者发送
案例对象已完成//完成时由结果生成参与者发送
类ResultCollector扩展了Actor{
def接收={
案例结果(值)=>resultQueue.put(部分(值))
案例完成=>resultQueue.put(无)
}
}
}
我使用一个选项[Result]来表示结果流的结尾,但没有。我已经尝试过偷看下一个元素并使用“完成”标志,但我希望有一个更简单的解决方案
奖金问题:
- 单元测试如何覆盖同步/异步实现,尤其是测试延迟的结果生成
- 如何使迭代器成为线程安全的
- 以下代码将确保要求的安全性。
演员的字段可以在演员的接收器中安全地修改。
所以resultQueue不应该在迭代器的字段中,而应该在Actor的字段中
// ResultCollector should be initialized.
// Initilize code is like...
// resultCollector ! Initialize(100)
class ResultStreamIterator(resultCollector: ActorRef) extends Iterator[Result] {
implicit val timeout: Timeout = ???
override def hasNext(): Boolean = Await.result(resultCollector ? HasNext, Duration.Inf) match {
case ResponseHasNext(hasNext) => hasNext
}
@scala.annotation.tailrec
final override def next(): Result = Await.result(resultCollector ? RequestResult, Duration.Inf) match {
case ResponseResult(result) => result
case Finished => throw new NoSuchElementException("There is not result.")
case WaitingResult => next()// should be wait for a moment.
}
}
case object RequestResult
case object HasNext
case class ResponseResult(result: Result)
case class ResponseHasNext(hasNext: Boolean)
case object Finished
case object WaitingResult
case class Initialize(expects: Int)
// This code may be more ellegant if using Actor FSM
// Acotr's State is (beforeInitialized)->(collecting)->(allCollected)
class ResultCollector extends Actor with Stash {
val results = scala.collection.mutable.Queue.empty[Result]
var expects = 0
var counts = 0
var isAllCollected = false
def beforeInitialized: Actor.Receive = {
case Initialize(n) =>
expects = n
if (expects != 0) context become collecting
else context become allCollected
unstashAll
case _ => stash()
}
def collecting: Actor.Receive = {
case RequestResult =>
if (results.isEmpty) sender ! WaitingResult
else sender ! ResponseResult(results.dequeue())
case HasNext => ResponseHasNext(true)
case result: Result =>
results += result
counts += 1
isAllCollected = counts >= expects
if (isAllCollected) context become allCollected
}
def allCollected: Actor.Receive = {
case RequestResult =>
if (results.isEmpty) sender ! Finished
else sender ! ResponseResult(results.dequeue())
case HasNext => ResponseHasNext(!results.isEmpty)
}
def receive = beforeInitialized
}
您可以使用变量存储下一个元素,并在两种方法的开头等待它:
private var nextNext: Option[Result] = null
def hasNext(): Boolean = {
if (nextNext == null) nextNext = resultQueue.take()
return !nextNext.isEmpty
}
def next(): Result = {
if (nextNext == null) nextNext = resultQueue.take()
if (nextNext.isEmpty) throw new NoSuchElementException()
val result = nextNext.get
nextNext = null
return result
}
我按照大郎的建议做了一些必要的修改。一般来说,我喜欢将
getNext()
和next()
实现为ask
消息发送给参与者的方法。这可以确保在任何时候只有一个线程修改队列
但是,我不确定此实现的性能,因为ask
和wait。result
将为hasNext()
和next()的每次调用创建两个线程
导入scala.concurrent.{wait,Future}
导入scala.concurrent.duration_
导入scala.language.postfix操作
导入akka.actor.{ActorRef,ActorSystem,Props,Stash}
导入akka.pattern.ask
导入akka.util.Timeout
case对象HasNext
案例对象GetNext
案例类结果(值:任意)
案例对象完成
类ResultCollector使用Stash扩展了Actor{
val queue=scala.collection.mutable.queue.empty[结果]
def收集:参与者。接收={
case HasNext=>if(queue.isEmpty)stash else sender!true
case GetNext=>if(queue.isEmpty)stash else sender!queue.dequeue
案例值:结果=>unstashAll;队列+=value
case Done=>unstashAll;上下文变为服务
}
def服务:参与者。接收={
case HasNext=>sender!queue.nonEmpty
case GetNext=>sender!{if(queue.nonEmpty)queue.dequeue else new NoSuchElementException}
}
def接收=收集
}
类ResultStreamIteration(resultCollector:ActorRef)扩展了迭代器{
隐式val超时:超时=超时(30秒)
override def hasNext():Boolean=Await.result(resultCollector?hasNext,Duration.Inf)匹配{
案例b:布尔=>b
}
override def next():Any=Await.result(resultCollector?GetNext,Duration.Inf)匹配{
案例结果(值:任意)=>值
案例e:Throwable=>throw e
}
}
对象测试扩展应用程序{
隐式val exec=scala.concurrent.ExecutionContext.global
val system=ActorSystem.create(“测试”)
val-actorRef=system.actorOf(Props[ResultCollector])
未来{
例如(如果你使用Java 8,实现一个Spliterator
和StreamSupport.stream(你的Spliterator,false/true)
你打算使用Java还是Scala?@Bubletan我更喜欢Scala。但是算法在Java和Scala中应该没什么关系(除了语法)。@fge我认为BlockingQueue.Spliterator()只需为队列中当前的元素创建一个迭代器。或者它是否也会像底层BlockingQueue一样阻塞?@goerlitz当队列为空时,您如何知道是否还会有实际结果?如果hasNext()
当队列为空时返回true,但在向其添加None
之后,接下来应该做什么
return?感谢您提示将队列放入Actor中。因此,正如您所说,它更安全、更干净,因为对队列的所有访问都是通过Actor的事件处理程序同步的。然后使用ask
请求、不同的状态也是有意义的(我必须查看FSM),和隐藏
。如果队列是参与者的字段,则不必使用BlockingQueue。如果队列是迭代器的字段,则实现迭代器的方法
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.language.postfixOps
import akka.actor.{ActorRef, ActorSystem, Props, Stash}
import akka.pattern.ask
import akka.util.Timeout
case object HasNext
case object GetNext
case class Result(value: Any)
case object Done
class ResultCollector extends Actor with Stash {
val queue = scala.collection.mutable.Queue.empty[Result]
def collecting: Actor.Receive = {
case HasNext => if (queue.isEmpty) stash else sender ! true
case GetNext => if (queue.isEmpty) stash else sender ! queue.dequeue
case value: Result => unstashAll; queue += value
case Done => unstashAll; context become serving
}
def serving: Actor.Receive = {
case HasNext => sender ! queue.nonEmpty
case GetNext => sender ! { if (queue.nonEmpty) queue.dequeue else new NoSuchElementException }
}
def receive = collecting
}
class ResultStreamIteration(resultCollector: ActorRef) extends Iterator {
implicit val timeout: Timeout = Timeout(30 seconds)
override def hasNext(): Boolean = Await.result(resultCollector ? HasNext, Duration.Inf) match {
case b: Boolean => b
}
override def next(): Any = Await.result(resultCollector ? GetNext, Duration.Inf) match {
case Result(value: Any) => value
case e: Throwable => throw e
}
}
object Test extends App {
implicit val exec = scala.concurrent.ExecutionContext.global
val system = ActorSystem.create("Test")
val actorRef = system.actorOf(Props[ResultCollector])
Future {
for (i <- 1 to 10000) actorRef ! Result(s"Result $i"); actorRef ! Done
}
val iterator = new ResultStreamIteration(actorRef)
while (iterator.hasNext()) println(iterator.next)
system.shutdown()
}