简体   繁体   English

异步迭代器

[英]Asynchronous Iterator

I have the following code: 我有以下代码:

while(slowIterator.hasNext()) {
  performLengthTask(slowIterator.next());
}

Because both iterator and task are slow it makes sense to put those into separate threads. 因为迭代器和任务都很慢,所以将它们放入单独的线程是有意义的。 Here is a quick and dirty attempt for an Iterator wrapper: 以下是Iterator包装器的快速而脏的尝试:

class AsyncIterator<T> implements Iterator<T> {
    private final BlockingQueue<T> queue = new ArrayBlockingQueue<T>(100);

    private AsyncIterator(final Iterator<T> delegate) {
      new Thread() {
        @Override
        public void run() {
          while(delegate.hasNext()) {
            queue.put(delegate.next()); // try/catch removed for brevity
          }
        }
      }.start();
    }

    @Override
    public boolean hasNext() {
      return true;
    }

    @Override
    public T next() {
        return queue.take(); // try/catch removed for brevity
    }
    // ... remove() throws UnsupportedOperationException
  }

However this implementation lacks support for "hasNext()". 但是,这种实现缺乏对“hasNext()”的支持。 It would be ok of course for the hasNext() method to block until it knows whether to return true or not. 当然可以阻止hasNext()方法阻塞,直到它知道是否返回true。 I could have a peek object in my AsyncIterator and I could change hasNext() to take an object from the queue and have next() return this peek. 我可以在我的AsyncIterator中有一个peek对象,我可以更改hasNext()从队列中获取一个对象并让next()返回此窥视。 But this would cause hasNext() to block indefinitely if the delegate iterator's end has been reached. 但是如果已达到委托迭代器的结尾,这将导致hasNext()无限期地阻塞。

Instead of utilizing the ArrayBlockingQueue I could of course do thread communication myself: 我自己可以自己进行线程通信,而不是使用ArrayBlockingQueue:

private static class AsyncIterator<T> implements Iterator<T> {

  private final Queue<T> queue = new LinkedList<T>();
  private boolean delegateDone = false;

  private AsyncIterator(final Iterator<T> delegate) {
    new Thread() {
      @Override
      public void run() {
        while (delegate.hasNext()) {
          final T next = delegate.next();
          synchronized (AsyncIterator.this) {
            queue.add(next);
            AsyncIterator.this.notify();
          }
        }
        synchronized (AsyncIterator.this) {
          delegateDone = true;
          AsyncIterator.this.notify();
        }
      }
    }.start();
  }

  @Override
  public boolean hasNext() {
    synchronized (this) {
      while (queue.size() == 0 && !delegateDone) {
        try {
          wait();
        } catch (InterruptedException e) {
          throw new Error(e);
        }
      }
    }
    return queue.size() > 0;
  }

  @Override
  public T next() {
    return queue.remove();
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException();
  }
}

However all the extra synchronizations, waits and notifys don't really make the code any more readable and it is easy to hide a race condition somewhere. 然而,所有额外的同步,等待和通知并没有真正使代码更具可读性,并且很容易在某处隐藏竞争条件。

Any better ideas? 有更好的想法吗?

Update 更新

Yes I do know about common observer/observable patterns. 是的我知道常见的观察者/可观察的模式。 However the usual implementations don't foresee an end to the flow of data and they are not iterators. 但是,通常的实现并不预见数据流的结束,它们不是迭代器。

I specifically want an iterator here, because actually the above mentioned loop exists in an external library and it wants an Iterator. 我特别想要一个迭代器,因为实际上上面提到的循环存在于一个外部库中,它需要一个迭代器。

This is a tricky one, but I think I got the right answer this time. 这是一个棘手的问题,但我想这次我得到了正确答案。 (I deleted my first answer.) (我删除了我的第一个答案。)

The answer is to use a sentinel. 答案是使用哨兵。 I haven't tested this code, and I removed try/catches for clarity: 我没有测试过这段代码,为了清楚起见,我删除了try / catches:

public class AsyncIterator<T> implements Iterator<T> {

    private BlockingQueue<T> queue = new ArrayBlockingQueue<T>(100);
    private T sentinel = (T) new Object();
    private T next;

    private AsyncIterator(final Iterator<T> delegate) {
        new Thread() {
            @Override
            public void run() {
                while (delegate.hasNext()) {
                    queue.put(delegate.next());
                }
                queue.put(sentinel);
            }
        }.start();
    }

    @Override
    public boolean hasNext() {
        if (next != null) {
            return true;
        }
        next = queue.take(); // blocks if necessary
        if (next == sentinel) {
            return false;
        }
        return true;
    }

    @Override
    public T next() {
        T tmp = next;
        next = null;
        return tmp;
    }

}

The insight here is that hasNext() needs to block until the next item is ready. 这里的见解是hasNext()需要阻塞,直到下一个项目准备好。 It also needs some kind of quit condition, and it can't use an empty queue or a boolean flag for that because of threading issues. 它还需要某种退出条件,并且由于线程问题,它不能使用空队列或布尔标志。 A sentinel solves the problem without any locking or synchronization. 哨兵在没有任何锁定或同步的情况下解决问题。

Edit: cached "next" so hasNext() can be called more than once. 编辑:缓存“下一步”,因此可以多次调用hasNext()。

Or save yourself the headache and use RxJava: 或者让自己避免头痛并使用RxJava:

import java.util.Iterator;

import rx.Observable;
import rx.Scheduler;
import rx.observables.BlockingObservable;
import rx.schedulers.Schedulers;

public class RxAsyncIteratorExample {

    public static void main(String[] args) throws InterruptedException {
        final Iterator<Integer> slowIterator = new SlowIntegerIterator(3, 7300);

        // the scheduler you use here will depend on what behaviour you
        // want but io is probably what you want
        Iterator<Integer> async = asyncIterator(slowIterator, Schedulers.io());
        while (async.hasNext()) {
            performLengthTask(async.next());
        }
    }

    public static <T> Iterator<T> asyncIterator(
            final Iterator<T> slowIterator,
            Scheduler scheduler) {

        final Observable<T> tObservable = Observable.from(new Iterable<T>() {
            @Override
            public Iterator<T> iterator() {
                return slowIterator;
            }
        }).subscribeOn(scheduler);

        return BlockingObservable.from(tObservable).getIterator();
    }

    /**
     * Uninteresting implementations...
     */
    public static void performLengthTask(Integer integer)
            throws InterruptedException {
        log("Running task for " + integer);
        Thread.sleep(10000l);
        log("Finished task for " + integer);
    }

    private static class SlowIntegerIterator implements Iterator<Integer> {
        private int count;
        private final long delay;

        public SlowIntegerIterator(int count, long delay) {
            this.count = count;
            this.delay = delay;
        }

        @Override
        public boolean hasNext() {
            return count > 0;
        }

        @Override
        public Integer next() {
            try {
                log("Starting long production " + count);
                Thread.sleep(delay);
                log("Finished long production " + count);
            }
            catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            return count--;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    private static final long startTime = System.currentTimeMillis();

    private static void log(String s) {
        double time = ((System.currentTimeMillis() - startTime) / 1000d);
        System.out.println(time + ": " + s);
    }
}

Gives me: 给我:

0.031: Starting long production 3
7.332: Finished long production 3
7.332: Starting long production 2
7.333: Running task for 3
14.633: Finished long production 2
14.633: Starting long production 1
17.333: Finished task for 3
17.333: Running task for 2
21.934: Finished long production 1
27.334: Finished task for 2
27.334: Running task for 1
37.335: Finished task for 1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM