Java 执行具有多次重试和超时的任务

标签 java timeout

我正在尝试创建一种方法来在最长时间内执行给定任务。如果未能在该时间内完成,则应在放弃之前重试多次。它还应该在每次尝试之间等待几秒钟。这是我的想法,我想对我的方法提出一些批评。他们使用 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;
}

最佳答案

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

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

因此,鉴于您有一个 ExecutorService,您可以调用:

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

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

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

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

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

我通常在这种情况下使用不同的策略:

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

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

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

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

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

关于Java 执行具有多次重试和超时的任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11751329/

相关文章:

c# - ASP.Net 注册表中的 MS Captcha 超时持续时间

timeout - 为长时间运行的 WCF 服务设置超时

c# - Azure Kubernetes .NET Core 应用程序到 Azure SQL 数据库间歇性错误 258

deployment - ClickOnce:DeploymentDownloadException:操作已超时

java - 优化 Android 粘性后台服务的内存使用

java - 在多个设备上分布计算

java - Hibernate - 使用 @Column(length= 40000) 时不会在 Ubuntu mysql 上创建表

java - 正则表达式去除所有方括号,除了特定前缀之后的方括号

java - ANTLRworks 中的 TestRig : how to use own classes?

android - 如何使 Asynctask 超时并关闭 ProgressDialog?