8

我正在尝试创建一种在最长时间内执行给定任务的方法。如果未能在该时间内完成,则应在放弃之前重试多次。它还应该在每次尝试之间等待几秒钟。这是我想出的,我想对我的方法提出一些批评。他们是使用 的更简单的方法ScheduledExecutorService还是我的方法就足够了?

public static <T> T execute(Callable<T> task, int tries, int waitTimeSeconds, int timeout) 
    throws InterruptedException, TimeoutException, Exception {

    Exception lastThrown = null;
    for (int i = 0; i < tries; i++) {
        try {
            final Future<T> future = new FutureTask<T>(task);
            return future.get(timeout, TimeUnit.SECONDS);
        } catch (TimeoutException ex) {
            lastThrown = ex;
        } catch (ExecutionException ex) {
            lastThrown = (Exception) ex.getCause();
        }
        Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSeconds));
    }
    if (lastThrown == null) {
        lastThrown = new TimeoutException("Reached max tries without being caused by some exception. " + task.getClass());
    }
    throw lastThrown;
}
4

2 回答 2

5

我认为,但我认为,如果您正在调度与网络相关的任务,则不应重试,而应最终并行运行它们。我稍后会描述这种另一种方法。

关于您的代码,您应该将任务传递给执行程序,或将 FutureTask 传递给线程。它不会产生线程或自行执行。如果你有一个 executor(见 ExecutorService),你甚至不需要 FutureTask,你可以简单地调度它并获得一个 callable。

所以,假设你有一个 ExecutorService,你可以调用:

Future<T> future = yourExecutor.submit(task);

Future.get(timeout) 将等待该超时并最终返回 TimeoutException,即使任务根本没有开始,例如,如果 Executor 已经忙于做其他工作并且找不到空闲线程。因此,您最终可能会尝试 5 次并等待几秒钟,而不会给任务运行机会。这可能是也可能不是您所期望的,但通常并非如此。也许您应该等待它启动,然后再给它一个超时。

此外,即使 Future 抛出 TimeoutException,您也应该显式取消它,否则它可能会继续运行,因为文档和代码都没有说明当 get 超时失败时它会停止。

即使您取消它,除非 Callable 已“正确编写”,否则它可能会继续运行一段时间。在这部分代码中您无能为力,请记住,没有线程可以“真正停止”另一个线程在 Java 中所做的事情,并且有充分的理由。

但是我认为您的任务主要与网络相关,因此它应该对线程中断做出正确反应。

我通常使用不同的策略是这样的情况:

  1. 我会写 public static T execute(Callable task, int maxTries, int timeout),所以任务,最大尝试次数(可能为 1),最大总超时(“我想在最多 10 秒内得到答案,不管多少次你试试,10 秒或什么都没有”)
  2. 我开始生成任务,将其交给执行者,然后调用 future.get(timeout/tries)
  3. 如果我收到结果,请将其返回。如果我收到异常,将重试(见下文)
  4. 但是,如果我超时,我不会取消未来,而是将其保存在列表中。
  5. 我检查是否已经过去了太多时间,或者是否有太多重试。在那种情况下,我取消列表中的所有期货并抛出异常,返回 null,无论如何
  6. 否则,我循环,再次安排任务(与第一个并行)。
  7. 见第 2 点
  8. 如果我没有收到结果,我会检查列表中的未来,也许之前生成的任务之一设法做到了。

假设您的任务可以多次执行(我想它们是,否则无法重试),对于网络内容,我发现此解决方案效果更好。

假设您的网络实际上非常繁忙,您请求网络连接,每次 2 秒重试 20 次。由于您的网络繁忙,20 次重试都无法在 2 秒内获得连接。但是,持续 40 秒的单次执行可能会设法连接和接收数据。这就像一个人在网速很慢的时候在一个页面上强制按f5,它不会有任何好处,因为每次浏览器都必须从头开始。

相反,我保持各种期货运行,第一个设法获取数据的期货将返回结果,其他期货将停止。如果第一个挂起,则第二个将起作用,或者第三个可能会起作用。

与浏览器相比,就像打开另一个选项卡并在不关闭第一个选项卡的情况下重试加载页面一样。如果网络很慢,第二个将需要一些时间,但不会停止第一个,最终会正确加载。相反,如果第一个选项卡被挂起,第二个选项卡将快速加载。无论哪个先加载,我们都可以关闭另一个选项卡。

于 2012-08-01T02:16:01.620 回答
1

调用你的线程execute将阻塞这么长时间。不确定这是否适合您。基本上,对于这些类型的任务,ScheduledExecutorService是最好的。您可以安排任务并指定时间。看看ScheduledThreadPoolExecutor

于 2012-08-01T06:14:02.217 回答