【问题标题】:Main program hang when read output stream of processB which starts another processC that runs forever当读取进程的输出流启动另一个永远运行的进程时,主程序挂起
【发布时间】:2012-02-16 10:58:56
【问题描述】:

共有三个 Java 应用程序,appB 只是启动 appC,它将永远运行。 appA 启动 appB 并读取其输出。 但 appA 永远不会退出。任何人都知道为什么会发生这种情况以及如何解决它。 如果我不读取 appA 中的流,它不会挂起。

我尝试了两种方法:

  1. 直接读取输出流(readViaInputStream)或BufferedReader(readViaBufferedReader)。

它们不起作用,输出将是:

// 如果我们调用它们,主应用程序挂起,输出将是:

//调用子进程之前

//调用子进程后

//主程序挂在这里。

// 从不输出 - “读取流完成。”

在 readViaInputStream 中,它在 BufferedInputStream.fill() 方法中的 fill() 方法处挂起 int n = getInIfOpen().read(buffer, pos, buffer.length - pos);它调用 FileInputStream 类的原生方法。

与 readViaBufferedReader 方法相同。

  1. 使用另一个线程读取输出流。

它也不起作用,输出将是:

//调用子进程之前

//调用子进程后

// 读取流完成。

// ===> 主程序挂起

非常感谢您的回复:)

代码如下 - 更新为使用下一条评论中提供的代码 Guillaume Polet。

public class MainApp {

public static enum APP {
    B, C;
}

public static void main(String[] args) throws Exception,
        InterruptedException {
    if (args.length > 0) {
        APP app = APP.valueOf(args[0]);
        switch (app) {
        case B:
            performB();
            break;
        case C:
            performC();
            break;
        }
        return;
    }
    performA();
}

private static void performA() throws Exception {
    String javaBin = "java";
    String[] cmdArray = { javaBin, "-cp",
            System.getProperty("java.class.path"), MainApp.class.getName(),
            APP.B.name() };
    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    builder.redirectErrorStream(true);
    final Process process = builder.start();

    process.getOutputStream().close();
    process.getErrorStream().close();

    // if we call this, the main app hangs, the output would be:
    // Before call child process
    // After call child process
    // the main program hangs here.
    // Never output - "Read stream finished."
    readViaInputStream(process);

    // if we call this, the main app hangs, the output would be:
    // Before call child process
    // After call child process
    // the main program hangs here.
    // Never output - "Read stream finished."

    // readViaBufferedReader(process);

    // if we call this, the main app still hangs, the output would be:
    // Before call child process
    // After call child process
    // Read stream finished.
    // ===> and the main program hang
    // readOutputViaAnotherThread(process);

    System.err.println("Read stream finished."); // never come here
}

private static void performB() throws Exception {
    System.out.println("Before call child process");
    String javaBin = "java";
    String[] cmdArray = { javaBin, "-cp",
            System.getProperty("java.class.path"), MainApp.class.getName(),
            APP.C.name() };
    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    Process process = builder.start();

    process.getInputStream().close();
    process.getOutputStream().close();
    process.getErrorStream().close();

    System.out.println("After call child process");
    System.exit(0); // no difference with or without this line.
}

private static void performC() throws Exception {
    Thread thread = new Thread() {
        @Override
        public void run() {
            int i = 0;
            while (true) {
                try {
                    Thread.sleep(60 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.err.println("child " + ++i);
            }
        }
    };
    thread.start();
    thread.join();

}

private static void readViaInputStream(final Process process)
        throws Exception {

    // System.err.println("exitValue: " + process.waitFor());
    InputStream is = process.getInputStream();
    int result;

    while ((result = is.read()) != -1) {
        System.err.println(result);
    }
}

private static void readViaBufferedReader(final Process process)
        throws Exception {
    BufferedReader in = new BufferedReader(new InputStreamReader(
            process.getInputStream(), "utf-8"));
    String result = "";
    while ((result = in.readLine()) != null) {
        System.err.println(result);
    }
}

private static void readOutputViaAnotherThread(final Process process)
        throws Exception {
    class ReadOutputStreamThread extends Thread {
        public void run() {

            running = true;
            try {
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(process.getInputStream(),
                                "utf-8"));
                String result = "";
                while (running && (result = in.readLine()) != null) {
                    System.err.println(result);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        };

        private volatile boolean running;

        public void shutdown() throws IOException {
            running = false;
            // this has no impact
            process.getInputStream().close();
            interrupt();
        }
    }

    ReadOutputStreamThread readOutputThread = new ReadOutputStreamThread();
    // if we set this readOutputThread as daemon, it works, but the thread
    // will remains run forever.
    // readOutputThread.setDaemon(true);
    readOutputThread.start();

    System.err.println("exitValue: " + process.waitFor());
    readOutputThread.shutdown();
}

}

【问题讨论】:

  • 你读过thread.join的javadoc吗?
  • 是的,子进程主线程似乎永远在等待。因为生成的线程不是守护进程,所以不需要加入调用。根据问题,问题不在于appC,而在于appA,它永远从输入流中读取。发生这种情况是因为流的另一部分没有关闭它。
  • 非常感谢您的回复。我提供的代码来自真实项目,进程 C 意味着永远运行。 - 唯一的区别是在我的真实项目中,一个 C# 应用程序正在启动应用程序 B 并读取其输出相同的问题。调试jdk源码后,貌似挂在BufferedReader.readLine(),调用BufferedReader中的fill()方法,最后调用sun.nio.cs.StreamDecoder.read()方法。 private void fill() throws IOException { do {n = in.read(cb, dst, cb.length - dst); // 它挂在这里 } while (n == 0);} 我不确定这是否是 JDK 错误。

标签: java process freeze


【解决方案1】:

当您的程序在BufferedReader.readLine() 中挂起时,这很可能是因为流未以行尾字符结尾。

readLine() 有两种方式继续和返回:

  • 有一个 EOL 字符,readLine() 返回该字符之前的行
  • 流关闭,readLine() 返回null

在您的情况下,这两种可能性都不成立,BufferedReader.readLine() 无法决定流中是否会出现更多字符,而 EOL 字符稍后或结束。所以它阻止并期待会发生什么。它预计会有更多字符进入,并且只有在下一个 EOL-char 进入或底层流关闭时才会返回。

尝试以下方法之一:

  • 不要使用readLine(),而是按字节读取方法
  • 确保进程将生成以 EOL 字符结尾的输出

另外请注意,您必须自行close all std-Streams - 即使不使用时也是如此。

【讨论】:

  • 巴尼,非常感谢您的回复。我跟踪代码,它似乎与 readLine 无关 - 我尝试使用按字节读取,甚至使用不同的线程来读取流,但没有任何效果。 - 请查看更新后的代码。
  • 谢谢.. 我的卡在 readline 上,我错过了关闭输出流。这对我有用.. :)
【解决方案2】:

编辑:好的,找到了解决方案: 您必须将输入流读取移动到另一个线程中并使用process.waitFor() 找出 B 何时退出。似乎您永远不会从子进程的输入流中获得 EOF,因此读取输入流永远不会结束。

对不起,这不是一个答案,只有一个 SSCCE,可以复制粘贴并启动,无需任何进一步的更改,并重现您的问题:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

public class Test5 {

    public static enum APP {
        B, C;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length > 0) {
            APP app = APP.valueOf(args[0]);
            switch (app) {
            case B:
                performB();
                break;
            case C:
                performC();
                break;
            }
            return;
        }
        performA();

    }

    private static void performC() throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                int i = 0;
                while (true) {
                    try {
                        Thread.sleep(60 * 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.err.println("child " + ++i);
                }
            }
        };
        thread.start();
        thread.join();

    }

    private static void performB() throws IOException {
        System.out.println("Before call child process");
        String javaBin = "java";
        String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.C.name() };
        ProcessBuilder builder = new ProcessBuilder(cmdArray);
        // Process process =
        builder.start();
        System.out.println("After call child process");
        System.exit(0); // no differnce with or without this line.

    }

    private static void performA() throws IOException, UnsupportedEncodingException {
        String javaBin = "java";
        String[] cmdArray = { javaBin, "-cp", System.getProperty("java.class.path"), Test5.class.getName(), APP.B.name() };
        ProcessBuilder builder = new ProcessBuilder(cmdArray);
        builder.redirectErrorStream(true);
        Process process = builder.start();
        BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
        String result = "";
        while ((result = in.readLine()) != null) {
            System.err.println(result);
        }
        System.err.println("Read stream finished."); // never come here
    }
}

【讨论】:

  • 嗨,Guillaume Polet:非常感谢您的回复。您提供的代码更好,更干净,也更易于运行。您提供的解决方案有效-在主程序中启动一个新线程(ReadOutputStreamThread)来读取输出流,调用process.waitFor(),子进程存在后,停止ReadOutputStreamThread。但是我在想为什么旧代码不起作用,试图调试JDK并弄清楚为什么会发生这种情况。
  • 对不起,但似乎使用另一个线程来读取输出将不起作用,除非将该线程设置为守护进程。
  • 将该线程设置为守护进程是不可接受的,因为它会使线程永远运行。我猜这是因为 readline 会挂起 - 即使关闭流也不起作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-25
  • 1970-01-01
  • 2015-01-09
  • 1970-01-01
  • 2014-08-22
  • 2015-03-22
  • 1970-01-01
相关资源
最近更新 更多