Java本地进程超时

48

目前我使用以下方式执行本地进程:

java.lang.Process process = Runtime.getRuntime().exec(command); 
int returnCode = process.waitFor();

假设我不想等待程序返回,而是希望在一定时间内终止程序。我该如何实现?


请在此处找到一个好的实践和一些解释:https://dev59.com/AFTTa4cB1Zd3GeqPvL1e - Wulfaz
6个回答

57
所有其他回答都是正确的,但使用FutureTask可以使其更加健壮和高效。
例如,
private static final ExecutorService THREAD_POOL 
    = Executors.newCachedThreadPool();

private static <T> T timedCall(Callable<T> c, long timeout, TimeUnit timeUnit)
    throws InterruptedException, ExecutionException, TimeoutException
{
    FutureTask<T> task = new FutureTask<T>(c);
    THREAD_POOL.execute(task);
    return task.get(timeout, timeUnit);
}

final java.lang.Process[] process = new Process[1];
try {
    int returnCode = timedCall(new Callable<Integer>() {
        public Integer call() throws Exception {
            process[0] = Runtime.getRuntime().exec(command); 
            return process[0].waitFor();
        }
    }, timeout, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    process[0].destroy();
    // Handle timeout here
}

如果您反复执行此操作,线程池会更加高效,因为它可以缓存线程。


这里的处理超时可能需要更加健壮。我有几种机制可以使用,但对于最简单的情况,请使用类似以下代码的方式:catch (TimeoutException e) { System.exit(-1);} - John Yeary
类型参数不能是原始类型。请用Integer替换int - naXa stands with Ukraine
尽管在指定的超时时间后会调用TimeoutException,但除非我们在抛出TimeoutException时调用destroy(),否则命令本身将继续在其他线程上后台执行(即使在执行器上调用了shutdownNow()也是如此)。 - dlauzon
当应用程序停止时,应该关闭THREAD_POOL(线程池),否则它可能会阻止JVM终止长达60秒(newCachedThreadPool()的工作线程的最大空闲时间),因为非守护进程工作线程仍然存活。 - Marcono1234

25

如果您使用的是Java 8或更高版本(Android的API 26或更高版本),您可以简单地使用带有超时的waitFor

Process p = ...
if(!p.waitFor(1, TimeUnit.MINUTE)) {
    //timeout - kill the process. 
    p.destroy(); // consider using destroyForcibly instead
}

在Android中,这仅适用于API 26及以上版本。 - Top-Master
@Bogdan,只有当waitFor(...)返回true时,您才可以调用p.exitValue()来获取退出代码。 - Marcono1234

19

这就是 Plexus CommandlineUtils 是如何做到的:

Process p;

p = cl.execute();

...

if ( timeoutInSeconds <= 0 )
{
    returnValue = p.waitFor();
}
else
{
    long now = System.currentTimeMillis();
    long timeoutInMillis = 1000L * timeoutInSeconds;
    long finish = now + timeoutInMillis;
    while ( isAlive( p ) && ( System.currentTimeMillis() < finish ) )
    {
        Thread.sleep( 10 );
    }
    if ( isAlive( p ) )
    {
        throw new InterruptedException( "Process timeout out after " + timeoutInSeconds + " seconds" );
    }
    returnValue = p.exitValue();
}

public static boolean isAlive( Process p ) {
    try
    {
        p.exitValue();
        return false;
    } catch (IllegalThreadStateException e) {
        return true;
    }
}

8
每隔 10 毫秒,它就依赖于 p.exitValue() 抛出 IllegalThreadStateException 来表示“仍在运行”? - Ogre Psalm33
1
@OgrePsalm33 这很糟糕,但遗憾的是在Java 7之前没有更好的方法。Java8提供了“p.isAlive()”。 - leonbloy
2
为什么要用循环?为什么不运行一个 TimerTask,在超时到期时仅检查“存活性”一次? - Stan

6

那么,使用Groovy的方式呢?

public void yourMethod() {
    ...
    Process process = new ProcessBuilder(...).start(); 
    //wait 5 secs or kill the process
    waitForOrKill(process, TimeUnit.SECONDS.toMillis(5));
    ...
}

public static void waitForOrKill(Process self, long numberOfMillis) {
    ProcessRunner runnable = new ProcessRunner(self);
    Thread thread = new Thread(runnable);
    thread.start();
    runnable.waitForOrKill(numberOfMillis);
}

protected static class ProcessRunner implements Runnable {
    Process process;
    private boolean finished;

    public ProcessRunner(Process process) {
        this.process = process;
    }

    public void run() {
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            // Ignore
        }
        synchronized (this) {
            notifyAll();
            finished = true;
        }
    }

    public synchronized void waitForOrKill(long millis) {
        if (!finished) {
            try {
                wait(millis);
            } catch (InterruptedException e) {
                // Ignore
            }
            if (!finished) {
                process.destroy();
            }
        }
    }
}

4

根据我的要求进行了一些修改。这里的超时时间为10秒。如果进程没有退出,则在10秒后将其销毁。

public static void main(String arg[]) {

    try {
        Process p = Runtime.getRuntime().exec("\"C:/Program Files/VanDyke Software/SecureCRT/SecureCRT.exe\"");
        long now = System.currentTimeMillis(); 
        long timeoutInMillis = 1000L * 10; 
        long finish = now + timeoutInMillis; 
        while ( isAlive( p ) ) { 
            Thread.sleep( 10 ); 
            if ( System.currentTimeMillis() > finish ) {
                p.destroy();
            }
        }
    } catch (Exception err) {
        err.printStackTrace();
    }
}

public static boolean isAlive( Process p ) {  
    try {  
        p.exitValue();  
        return false;  
    } catch (IllegalThreadStateException e) {  
        return true;  
    }  
}  

2
您需要一个第二个线程来中断调用.waitFor()的线程。 为了使其更加健壮,需要进行一些非平凡的同步,但基本原理如下:
TimeoutThread:
 Thread.sleep(timeout);
 processThread.interrupt();

进程线程:

  try {
      proc.waitFor(); 
    } catch (InterruptedException e) {
       proc.destroy();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接