代码之家  ›  专栏  ›  技术社区  ›  carrier

如何包装方法,以便在方法超过指定超时时终止其执行?

  •  15
  • carrier  · 技术社区  · 16 年前

    我有一个我想调用的方法。不过,我正在寻找一种干净、简单的方法来杀死它,或者在执行时间过长时强制它返回。

    我在用Java。

    举例说明:

    logger.info("sequentially executing all batches...");
    for (TestExecutor executor : builder.getExecutors()) {
    logger.info("executing batch...");
    executor.execute();
    }
    

    我想 TestExecutor 类应该 implement Callable 继续往那个方向走。

    但我只想停下来 executor.execute() 如果时间太长。

    建议。。。?

    编辑

    收到的许多建议都假设正在执行的方法包含某种类型的循环,并且可以定期检查变量。 然而,事实并非如此。所以不一定是干净的东西,只要是可以接受的,就会停止执行。

    7 回复  |  直到 10 年前
        1
  •  11
  •   Alex    16 年前

    你应该看看这些课程: FutureTask ,请 Callable 我是说, Executors

    下面是一个例子:

    public class TimeoutExample {
        public static Object myMethod() {
            // does your thing and taking a long time to execute
            return someResult;
        }
    
        public static void main(final String[] args) {
            Callable<Object> callable = new Callable<Object>() {
                public Object call() throws Exception {
                    return myMethod();
                }
            };
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            Future<Object> task = executorService.submit(callable);
            try {
                // ok, wait for 30 seconds max
                Object result = task.get(30, TimeUnit.SECONDS);
                System.out.println("Finished with result: " + result);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            } catch (TimeoutException e) {
                System.out.println("timeout...");
            } catch (InterruptedException e) {
                System.out.println("interrupted");
            }
        }
    }
    
        2
  •  8
  •   Dan Dyer    16 年前

    爪哇的 interruption mechanism 是为这种情况准备的。如果要中止的方法正在执行循环,只需让它检查线程的 interrupted status 每次迭代。如果中断了,抛出中断异常。

    那么,当你想中止时,你只需要调用 interrupt 在适当的螺纹上。

    或者,您可以使用 approach sun建议作为不推荐的stop方法的替代方法。这不涉及抛出任何异常,方法只会正常返回。

        3
  •  7
  •   Tim Stewart    16 年前

    我假设在下面的语句中使用多个线程。

    我在这方面做过一些阅读,大多数作者都说,不应该再多写一篇文章。

    如果您要终止的函数可以设计为定期检查变量或同步基元,然后在设置了该变量或同步基元时干净地终止,那将是非常干净的。然后某种类型的监视线程可以休眠几毫秒,然后设置变量或同步原语。

        4
  •  3
  •   krakatoa    16 年前

    真的,你不能…唯一的方法是使用thread.stop,商定一个“合作”方法(例如偶尔检查thread.isInterrupted,或者调用抛出InterruptedException的方法,例如thread.sleep()),或者以某种方式在另一个JVM中完全调用该方法。

    对于某些类型的测试,调用stop()是可以的,但它可能会损坏测试套件的状态,因此如果要避免交互影响,必须在每次调用stop()之后重新启动jvm。

    为了更好地描述如何实现协作方法,请查看 Sun's FAQ on the deprecated Thread methods .

    作为现实生活中这种方法的一个例子, Eclipse RCP's Job API's “iprogressmonitor”对象允许某些管理服务(通过“cancel”方法)向子进程发出它们应该停止的信号。当然,这依赖于这些方法来定期检查被取消的方法,而这些方法往往无法做到。

    一种混合的方法可能是用中断很好地询问线程,然后在几秒钟后用停止来坚持。同样,您不应该在生产代码中使用STOP,但在这种情况下可能是好的,尤其是如果您在不久之后退出JVM。

    为了测试这种方法,我编写了一个简单的工具,它使用一个runnable并尝试执行它。请随意评论/编辑。

    public void testStop(Runnable r) {
        Thread t = new Thread(r);
        t.start();
        try {
            t.join(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    
        if (!t.isAlive()) {
            System.err.println("Finished on time.");
            return;
        }
    
        try {
            t.interrupt();
            t.join(2000);
            if (!t.isAlive()) {
                System.err.println("cooperative stop");
                return;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.err.println("non-cooperative stop");
        StackTraceElement[] trace = Thread.getAllStackTraces().get(t);
        if (null != trace) {
            Throwable temp = new Throwable();
            temp.setStackTrace(trace);
            temp.printStackTrace();
        }
        t.stop();
        System.err.println("stopped non-cooperative thread");
    }
    

    为了测试它,我编写了两个相互竞争的无限循环,一个是协作循环,另一个从不检查线程的中断位。

    public void cooperative() {
        try {
            for (;;) {
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            System.err.println("cooperative() interrupted");
        } finally {
            System.err.println("cooperative() finally");
        }
    }
    
    public void noncooperative() {
        try {
            for (;;) {
                Thread.yield();
            }
        } finally {
            System.err.println("noncooperative() finally");
        }
    }
    

    最后,我编写了测试(junit 4)来练习它们:

    @Test
    public void testStopCooperative() {
        testStop(new Runnable() {
            @Override
            public void run() {
                cooperative();
            }
        });
    }
    
    @Test
    public void testStopNoncooperative() {
        testStop(new Runnable() {
            @Override
            public void run() {
                noncooperative();
            }
        });
    }
    

    我以前从未使用过thread.stop(),所以我不知道它的操作。它通过抛出一个threaddeath对象来运行目标线程。这扩展了错误。因此,虽然它并不总是干净地工作,但它通常会给简单的程序留下一个相当合理的程序状态。例如,调用任何finally块。如果你想成为一个真正的混蛋,你可以 抓住 线程死亡(或错误),并继续运行,无论如何!

    如果没有别的,这真的让我希望更多的代码遵循iprogressmonitor方法-向 可以 花点时间,鼓励方法的实现者偶尔轮询monitor对象,看看用户是否希望系统放弃。我将在将来尝试遵循这种模式,特别是可能是交互式的方法。当然,您不一定事先知道将以这种方式使用哪些方法,但我猜这就是探查器的用途。

    至于“完全启动另一个jvm”方法,这将需要更多的工作。我不知道是否有人编写了委托类加载器,或者jvm中是否包含了委托类加载器,但是这种方法需要这样做。

        5
  •  1
  •   John Gardner    16 年前

    没有人直接回答,所以这里是我能给你的最接近的东西,在短期的psuedo代码:

    将方法包装为可运行/可调用的。如果要停止,方法本身必须检查中断状态(例如,如果此方法是循环,则在循环内检查thread.currentThread().IsInterrupted,如果是,则停止循环(但不要检查每个迭代,否则只会减慢速度)。 在包装方法中,使用thread.join(超时)等待方法运行的时间。或者,在那里的循环中,如果您需要在等待时执行其他操作,请使用较小的超时重复调用join。如果方法未完成,则在连接后,使用上述建议中止fast/clean。

    所以代码方面,旧代码:

    void myMethod()
    {
        methodTakingAllTheTime();
    }
    

    新代码:

    void myMethod()
    {
        Thread t = new Thread(new Runnable()
            {
                public void run()
                {
                     methodTakingAllTheTime(); // modify the internals of this method to check for interruption
                }
            });
        t.join(5000); // 5 seconds
        t.interrupt();
    }
    

    但同样,要使它正常工作,您仍然需要修改所有时间的方法,否则该线程将在调用中断后继续运行。

        6
  •  0
  •   davidswelt    12 年前

    正确的答案是,我相信,创建一个runnable来执行子程序,并在一个单独的线程中运行它。runnable可能是一个futuretask,您可以在超时的情况下运行(“get”方法)。如果超时,你会得到一个TimeoutException,我建议你

    • call thread.interrupt()尝试以半合作的方式结束它(许多库调用似乎对此很敏感,因此它可能会工作)
    • 稍等(线程。睡眠(300))
    • 然后,如果线程仍然处于活动状态(thread.is active()),则调用thread.stop()。这是一个被弃用的方法,但显然是城里唯一一个没有运行独立进程的游戏。

    在我的应用程序中,我运行由初学者编写的不可信、不合作的代码,我执行上述操作,确保被杀死的线程永远无法(写入)访问任何在其死亡后仍然存在的对象。这包括包含被调用方法的对象,如果发生超时,将丢弃该对象。(我告诉我的学生要避免超时,因为他们的经纪人将被取消资格。)我不确定内存泄漏…

    我区分长运行时(方法终止)和硬超时-硬超时更长,是为了捕捉代码根本不终止的情况,而不是慢的情况。

    从我的研究来看,java似乎没有一个运行非合作代码的非弃用条款,在某种程度上,这是安全模型中的一个漏洞。要么我可以运行外来代码并控制它所拥有的权限(securitymanager),要么我不能运行外来代码,因为它可能会占用整个cpu,而没有不推荐的方法来停止它。

    double x = 2.0;  
    while(true) {x = x*x}; // do not terminate
    System.out.print(x); // prevent optimization
    
        7
  •  0
  •   WVrock    11 年前

    我能想出一个不太好的方法来做这件事。如果你能检测出它花费了太多的时间,你可以让方法在每一步检查一个布尔值。如果需要花费太多时间(我无法帮助您),请让程序将boolean too much time的值更改为true。然后用这样的方法:

     Method(){
     //task1
    if (tooMuchTime == true) return;
     //task2
    if (tooMuchTime == true) return;
     //task3
    if (tooMuchTime == true) return;
    //task4
    if (tooMuchTime == true) return;
    //task5
    if (tooMuchTime == true) return;
    //final task
      }