【问题标题】:What is the best way to break out of iteration after a specific time?在特定时间后打破迭代的最佳方法是什么?
【发布时间】:2014-09-21 05:00:24
【问题描述】:

我正在迭代 Iterator,其中 hasNext() 永远不会返回 false。但是,在指定时间(比如说 20 秒)之后,我想停止迭代。问题是Iteratornext()方法是阻塞的,但即便如此,在指定时间后,我只需要停止迭代即可。

这是我的示例 IterableIterator 来模拟我的问题。

public class EndlessIterable implements Iterable<String> {
    static class EndlessIterator implements Iterator<String> {
         public boolean hasNext() { return true; }
         public String next() { 
             return "" + System.currentTimeMillis(); //in reality, this code does some long running task, so it's blocking
         }
    }
   public Iterator<String> iterator() { return new EndlessIterator(); }
}

这是我要测试的代码。

EndlessIterable iterable = new EndlessIterable();
for(String s : iterable) { System.out.println(s); }

我想把代码/逻辑放到Iterable类中创建一个Timer,所以指定的时间到了,就会抛出异常,停止迭代。

public class EndlessIterable implements Iterable<String> {
    static class EndlessIterator implements Iterator<String> {
        public boolean hasNext() { return true; }
        public String next() { 
            try { Thread.sleep(2000); } catch(Exception) { } //just sleep for a while
            return "" + System.currentTimeMillis(); //in reality, this code does some long running task, so it's blocking
        }
    }
    static class ThrowableTimerTask extends TimerTask {
        private Timer timer;
        public ThrowableTimerTask(Timer timer) { this.timer = timer; }
        public void run() {
            this.timer.cancel();
            throw new RuntimeException("out of time!");
        }
    }
    private Timer timer;
    private long maxTime = 20000; //20 seconds
    public EndlessIterable(long maxTime) {
        this.maxTime = maxTime;
        this.timer = new Timer(true);
    }
    public Iterator<String> iterator() { 
        this.timer.schedule(new ThrowableTimerTask(this.timer), maxTime, maxTime);
        return new EndlessIterator();
    }
}

然后我尝试如下测试此代码。

EndlessIterable iterable = new EndlessIterable(5000);
try {
    for(String s : iterable) { System.out.println(s); }
} catch(Exception) {
    System.out.println("exception detected: " + e.getMessage());
}
System.out.println("done");

我注意到的是RuntimeException在时间到之后被抛出,然而,

  • for 循环继续进行,
  • 永远不会到达 catch 块,并且
  • 我永远不会到达代码的末尾(打印完成)。

有什么策略、方法或设计模式可以解决我所描述的这个问题吗?

请注意

  • 在我的实际代码中,我无法控制Iterator
  • 我只能控制Iterable 和实际迭代

【问题讨论】:

  • 您在一个完全不相关的线程中抛出异常。当然,这不会影响您的迭代。

标签: java multithreading timer iterator iterable


【解决方案1】:

您在工作中使用了错误的工具。如果您希望操作超时,则必须将检查添加到操作中。建议将普通迭代器逻辑与超时检查分开,这似乎符合您无法更改 Iterator 实现的说法。为此,请使用 decorator/delegation 模式:

// an iterator wrapping another one adding the timeout functionality
class TimeOutIterator<T> implements Iterator<T> {
  final Iterator<T> source;
  final long deadline;

  public TimeOutIterator(Iterator<T> dataSource, long timeout, TimeUnit unit) {
    source=dataSource;
    deadline=System.nanoTime()+unit.toNanos(timeout);
  }
  private void check() {
    if(System.nanoTime()-deadline >= 0)
      throw new RuntimeException("timeout reached");
  }
  public boolean hasNext() {
    check();
    return source.hasNext();
  }
  public T next() {
    check();
    return source.next();
  }
  public void remove() {
    check();
    source.remove();
  }
}

所以你可以实现你的迭代:

public class EndlessIterable implements Iterable<String> {
  static class EndlessIterator implements Iterator<String> {
   public boolean hasNext() { return true; }
   public String next() { 
     // dummy code illustrating the long running task
     try { Thread.sleep(2000); } catch(Exception e) { }
     return "" + System.currentTimeMillis();
   }
   public void remove() { throw new UnsupportedOperationException(); }
  }
  private long maxTime;
  private TimeUnit unit;

  public EndlessIterable(long maxTime, TimeUnit timeUnit) {
    this.maxTime = maxTime;
    this.unit = timeUnit;
  }
  public Iterator<String> iterator() { 
    return new TimeOutIterator<>(new EndlessIterator(), maxTime, unit);
  }
}

那么测试代码如下:

// should timeout after five seconds
EndlessIterable iterable = new EndlessIterable(5, TimeUnit.SECONDS);
try {
 for(String s : iterable) { System.out.println(s); }
} catch(Exception e) {
  System.out.println("exception detected: " + e);
}
System.out.println("done");

【讨论】:

  • 这个解决方案很棒。但是,还有一个小问题:如果在这个例子中,我告诉程序在 5 秒后超时,但第一次调用 next() 需要 10 秒(即将 Thread.sleep(2000) 更改为 Thread.sleep( 10000))?根据这段代码,第一次调用next() 会返回,只有第二次调用next() 才会停止迭代。关于解决这个问题的任何建议?
  • 意思是,如果第一次调用next()需要10秒,我已经希望for循环在5秒时停止,不必等待它返回然后再次调用next() .
  • 这个问题没有通用的解决方案。结束任意代码执行的唯一方法是使用Thread.stop(),它已被弃用,因为它会让程序处于未定义的、可能不一致的状态。另一种选择是Thread.interrupt(),它需要在被中断的代码中提供积极的支持,例如代码必须定期轮询中断状态并相应地结束,或者它必须使用阻塞操作,在线程中断时抛出InterruptedException(并且代码必须在InterruptedException发生时做出相应的反应。
  • 因此,对于使用Thread.sleep() 中断的示例代码,我想对于您的实际用例,情况会有所不同。但是,即使代码支持中断,它的执行也可能需要一些时间才能达到对其做出反应的程度。唯一的解决方案是不依赖于超时的确切实现。如果您有必须及时执行的操作,请在不同的线程中执行它并通过循环结束或计时器触发它,以先到者为准。
【解决方案2】:

没有停止所有阻塞操作的通用方法。有些调用是可中断的,也就是说,当另一个线程在工作线程上调用interrupt() 时,它们会以错误或部分结果中止操作。其他时候,有一些黑客会导致操作终止;例如,如果一个线程被阻止读取套接字,另一个线程可以关闭套接字。

但是,在这种情况下,您可以执行在另一个线程中生成下一个元素所需的阻塞操作。将实现迭代器以使用来自BlockingQueue 的这些元素。这样,迭代线程将在超时到期时立即返回,而不是为最后一个元素等待无限量的额外时间。工作线程可能会继续一段时间,但它可以在生成每个元素之前检查一个标志以确定是否继续。

您已经接受了一个答案,但如果您对这种方法感兴趣,请告诉我,我可以编写一些代码。

【讨论】:

    【解决方案3】:

    您可以在 Thread 中启动此任务,在另一个 Thread 中您将启动类似

    Thread.sleep(20)
    

    然后取消第一个线程

    【讨论】:

    • 能否提供一个完整的例子?
    • 取消线程早已不再是可接受的行为,因为它们会使线程处于无效状态,从而破坏应用程序的数据或使 JVM 崩溃。
    【解决方案4】:

    有一个线程在你的时间段内休眠,然后在执行迭代的线程上调用Thread.interrupt(),然后每次通过循环你可以检查线程是否isInterrupted(),如果它曾经退出循环真的。

    您也可以使用Timer 而不是内联调用Thread.sleep();哪种方法更适合您将取决于您的调用线程正在做什么等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-30
      • 2012-05-30
      • 2016-05-05
      相关资源
      最近更新 更多