77

ローカル ホストでコマンドを実行するために使用している方法があります。メソッドにタイムアウト パラメータを追加して、呼び出されたコマンドが妥当な時間内に終了しない場合にメソッドがエラー コードを返すようにしたいと考えています。これまでのところ、タイムアウトする機能がない場合は次のようになります。

public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError)
    throws IOException, InterruptedException
{
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(commandLine);

    if (printOutput)
    {
        BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        System.out.println("Output:  " + outputReader.readLine());
    }

    if (printError)
    {
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        System.out.println("Error:  " + errorReader.readLine());
    }

    return process.waitFor();
}

タイムアウトパラメータを実装する良い方法を誰かが提案できますか?

4

17 に答える 17

91

Java 8 以降を使用している場合は、単純に新しいwaitFor を timeout とともに使用できます。

Process p = ...
if(!p.waitFor(1, TimeUnit.MINUTES)) {
    //timeout - kill the process. 
    p.destroy(); // consider using destroyForcibly instead
}
于 2014-03-12T20:02:40.197 に答える
55
public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError,
                                     final long timeout)
      throws IOException, InterruptedException, TimeoutException {
  Runtime runtime = Runtime.getRuntime();
  Process process = runtime.exec(commandLine);
  /* Set up process I/O. */
  ... 
  Worker worker = new Worker(process);
  worker.start();
  try {
    worker.join(timeout);
    if (worker.exit != null)
      return worker.exit;
    else
      throw new TimeoutException();
  } catch(InterruptedException ex) {
    worker.interrupt();
    Thread.currentThread().interrupt();
    throw ex;
  } finally {
    process.destroyForcibly();
  }
}

private static class Worker extends Thread {
  private final Process process;
  private Integer exit;
  private Worker(Process process) {
    this.process = process;
  }
  public void run() {
    try { 
      exit = process.waitFor();
    } catch (InterruptedException ignore) {
      return;
    }
  }  
}
于 2009-04-30T18:22:47.413 に答える
15

エリクソンの回答に続いて、同じことを行うためのより一般的な方法を作成しました。

public class ProcessWithTimeout extends Thread
{
    private Process m_process;
    private int m_exitCode = Integer.MIN_VALUE;

    public ProcessWithTimeout(Process p_process)
    {
        m_process = p_process;
    }

    public int waitForProcess(int p_timeoutMilliseconds)
    {
        this.start();

        try
        {
            this.join(p_timeoutMilliseconds);
        }
        catch (InterruptedException e)
        {
            this.interrupt();
        }

        return m_exitCode;
    }

    @Override
    public void run()
    {
        try
        { 
            m_exitCode = m_process.waitFor();
        }
        catch (InterruptedException ignore)
        {
            // Do nothing
        }
        catch (Exception ex)
        {
            // Unexpected exception
        }
    }
}

今、あなたがしなければならないことは次のとおりです。

Process process = Runtime.getRuntime().exec("<your command goes here>");
ProcessWithTimeout processWithTimeout = new ProcessWithTimeout(process);
int exitCode = processWithTimeout.waitForProcess(5000);

if (exitCode == Integer.MIN_VALUE)
{
    // Timeout
}
else
{
    // No timeout !
}
于 2013-10-09T12:40:51.153 に答える
5

新しい Java 8 メソッドを使用できない waitFor(long timeout, TimeUnit unit)(Android を使用している、または単にアップグレードできないなどの理由で) 場合は、JDK ソース コードから単純にリッピングして、utils ファイルのどこかに追加できます。

public boolean waitFor(long timeout, TimeUnit unit, final Process process)
            throws InterruptedException
    {
        long startTime = System.nanoTime();
        long rem = unit.toNanos(timeout);

        do {
            try {
                process.exitValue();
                return true;
            } catch(IllegalThreadStateException ex) {
                if (rem > 0)
                    Thread.sleep(
                            Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
            }
            rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
        } while (rem > 0);
        return false;
    }

JDK8 ソース コードから元のコードに加えた唯一の変更は 、プロセスからメソッドをProcess呼び出すことができるようにパラメーターを追加したことです。exitValue

プロセスがまだ終了していない場合、メソッドexitValueは を直接返すかスローしようとします。IllegalThreadStateExceptionその場合、受信したタイムアウトを待って終了します。

このメソッドはブール値を返すため、false が返された場合は、プロセスを手動で強制終了する必要があることがわかります。

この方法は、上記のどの方法よりも簡単に思えます (確かに、waitFor への直接呼び出しを期待してください)。

于 2015-12-01T02:13:09.993 に答える
3

小さなアプリ向けの軽量ソリューション:

public class Test {
    public static void main(String[] args) throws java.io.IOException, InterruptedException {   
        Process process = new ProcessBuilder().command("sleep", "10").start();

        int i=0;
        boolean deadYet = false;
        do {
            Thread.sleep(1000);
            try {
                process.exitValue();
                deadYet = true;
            } catch (IllegalThreadStateException e) {
                System.out.println("Not done yet...");
                if (++i >= 5) throw new RuntimeException("timeout");
            }
        } while (!deadYet);
    }
}
于 2013-07-25T21:20:53.257 に答える
2

また、ワーカーの実装をテストし、魅力のように機能します。プロセスioの処理で、stdeとstdoを処理するスレッドを追加しました。ワーカースレッドがタイムアウトした場合は、ioスレッドも終了します。

Process p = Runtime.getRuntime().exec(cmd.trim());

            //setup error and output stream threads
            CommandStreamThread eStream = new CommandStreamThread(p.getErrorStream(), "STDE");            
            CommandStreamThread oStream = new CommandStreamThread(p.getInputStream(), "STDO");

            // kick them off
            eStream.start();
            oStream.start();

            //setup a worker thread so we can time it out when we need
            CommandWorkerThread worker=new CommandWorkerThread(p);
            worker.start();

            try {
                worker.join(this.getTimeout());
                if (worker.getExit() != null)
                    return worker.getExit();
                else
                    throw new TimeoutException("Timeout reached:"+this.getTimeout()+" ms");
            } catch(InterruptedException ex) {
                eStream.interrupt();
                oStream.interrupt();
                worker.interrupt();
                Thread.currentThread().interrupt();
                throw ex;
            } finally {
                p.destroy();
            }
于 2010-05-21T18:22:07.037 に答える
2

最初に背景情報として、コマンドの実行中にタイムアウトが発生するという問題に遭遇しました。これは、実行しようとしたプログラムが、エラーが発生した場合にデバッグ情報やエラー情報を出力せず、内部で再試行を続けてプロセスが停止するためです。再試行中にエラーや出力ストリームが発生しなかったためです。

したがって、process.exec()またはの後process.start()

この線で永遠に行き詰まります。

BufferedReader input = new BufferedReader(newInputStreamReader(process.getInputStream()));

Java 1.8 with public boolean waitFor(long timeout,TimeUnit unit)method に従って、指定されたタイムアウト後に「理想的に」タイムアウトする必要がありますが、私の場合、何らかの理由でタイムアウトにならなかったのは、アプリケーションを Windows サービスとして実行していたためである可能性があります (ユーザー権限とすべてを確認しましたアカウントですが、役に立ちませんでした)。

そこで、以下のロジックを使用して実装しようとしました。ここでは、入力ストリームとinput.ready()タイムアウト フラグをチェックし続けます。

コード:

public boolean runCommand() throws IOException, InterruptedException, Exception {
    StringBuilder rawResponse = new StringBuilder();
    System.out.println("Running Command " + Arrays.toString(command));
    ProcessBuilder processBuilder = new ProcessBuilder(Arrays.asList(command));
    processBuilder.redirectErrorStream(true);
    Process process = processBuilder.start(); //Executing the process
    BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
    waitForTimeout(input, process); //Waiting for Timout
    String line;
    while ((line = input.readLine()) != null) {
        rawResponse.append(line).append("\n");
    }
    return true;
}


//Timeout method 
private void waitForTimeout(BufferedReader input, Process process) throws InterruptedException, Exception {
    int timeout = 5;
    while (timeout > 0) {
        if (!process.isAlive() || input.ready()) {
            break;
        } else {
            timeout--;
            Thread.sleep(1000);
            if (timeout == 0 && !input.ready()) {
                destroyProcess(process);
                throw new Exception("Timeout in executing the command "+Arrays.toString(command));
            }
        }
    }
}
于 2015-10-21T16:58:08.587 に答える
2

デリゲートとして実装し、完了するのにしきい値を超える場合は呼び出しを失敗させます。

于 2009-04-30T18:04:26.287 に答える
2

タイマー (または Sleep()) を別のスレッドで使用するか、使用可能な場合はイベント キューで使用してみてください。

于 2009-04-30T18:04:49.123 に答える
2

これを行うにはさまざまな方法がありますが、Executor の使用を検討します。終了値または例外をスレッドから元の呼び出し元に渡すことをカプセル化するのに役立ちます。

    final Process p = ...        
    Callable<Integer> call = new Callable<Integer>() {
    public Integer call() throws Exception {
        p.waitFor();
        return p.exitValue();
      }
    };
    Future<Integer> ft = Executors.newSingleThreadExecutor().submit(call);
    try {
      int exitVal = ft.get(2000L, TimeUnit.MILLISECONDS);
      return exitVal;
    } catch (TimeoutException to) {
      p.destroy();
      throw to;
    }

待機がタイムアウトし、destroy() を呼び出す直前にプロセスが終了するという競合状態を回避できないと思います。

于 2009-04-30T19:45:01.350 に答える
0

必要な時間スリープし、スリープ後にexecuteCommandLineメソッドでループするブール値を変更するスレッドを起動できます。

そのようなもの(テストもコンパイルもされていません。このソリューションはプロトタイプであり、必要に応じてリファクタリングする必要があります):

public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError)
    throws IOException, InterruptedException
{
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(commandLine);

    if (printOutput)
    {
        BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        System.out.println("Output:  " + outputReader.readLine());
    }

    if (printError)
    {
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        System.out.println("Error:  " + errorReader.readLine());
    }

    ret = -1;
    final[] b = {true};
    new Thread(){
       public void run(){
           Thread.sleep(2000); //to adapt
           b[0] = false;
       }
    }.start();
    while(b[0])
    {
          ret = process.waitFor();
    }

    return ret;
}
于 2009-04-30T18:12:45.063 に答える
0

ここにStreamThreadがあります

public class CommandStreamThread extends Thread{
        private InputStream iStream;
        private String cPrompt;

        CommandStreamThread (InputStream is, String cPrompt)
        {
            this.iStream = is;
            this.cPrompt = cPrompt;
        }

        public void run()
        {
            try
            {
                InputStreamReader streamReader= new InputStreamReader(this.iStream);
                BufferedReader reader = new BufferedReader(streamReader);


                String linesep=System.getProperty("line.separator");
                String line=null;
                while ((line=reader.readLine())!=null){
                    System.out.println(line);
                    //Process the next line seperately in case this is EOF is not preceded by EOL
                    int in;
                    char[] buffer=new char[linesep.length()];
                    while ( (in = reader.read(buffer)) != -1){
                        String bufferValue=String.valueOf(buffer, 0, in);
                        System.out.print(bufferValue);
                        if (bufferValue.equalsIgnoreCase(linesep))
                            break;
                    }
                }

                //Or the easy way out with commons utils!
                //IOUtils.copy(this.iStream, System.out);


              } catch (Exception e){
                    e.printStackTrace();  
              }
        }

        public InputStream getIStream() {
            return iStream;
        }

        public void setIStream(InputStream stream) {
            iStream = stream;
        }

        public String getCPrompt() {
            return cPrompt;
        }

        public void setCPrompt(String prompt) {
            cPrompt = prompt;
        }


}
于 2010-05-24T13:05:21.797 に答える
0

Apache Commons Exec は、それを行うのに役立ちます。

http://commons.apache.org/proper/commons-exec/tutorial.htmlを参照してください。

String line = "your command line";
CommandLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
executor.setWatchdog(watchdog);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
int exitValue = executor.execute(cmdLine);
System.out.println(exitValue);
System.out.println(outputStream.toString());
于 2015-09-11T04:49:45.277 に答える