8

Java アプリ (Win7 64 ビットの Eclipse Helios 内でデバッグ モードで実行されている Tomcat サーバーの一部) 内から wkhtmltopdf を起動しています。

String cmd[] = {"wkhtmltopdf", htmlPathIn, pdfPathOut};
Process proc = Runtime.getRuntime().exec( cmd, null );

proc.waitFor();

しかし、waitFor()決して戻りません。Windows タスク マネージャーでプロセスを確認できます (exec() に渡したコマンド ラインでは問題ないようです)。そしてそれは動作します。wkhtmltopdf は、期待どおりの場所に、期待どおりの PDF を生成します。プロセスがまだ実行中であっても(手動で終了する前に)、開いたり、名前を変更したりできます。

コマンドラインからは、すべて問題ありません。

c:\wrk>wkhtmltopdf C:\Temp\foo.html c:\wrk\foo.pdf
ページを読み込んでいます (1/6)
ページを数える (2/6)
リンクの解決 (4/6)
ヘッダーとフッターの読み込み (5/6)
ページの印刷 (6/6)
終わり

プロセスは問題なく終了し、人生は続きます。

ではruntime.exec()、wkhtmltopdf が終了しない原因は何でしょうか?

proc.getInputStream() を取得して "Done" を探すこともできますが、それは... 卑劣です。もっと一般的なものが欲しい。

作業ディレクトリの有無にかかわらず exec() を呼び出しました。空の「env」配列の有無にかかわらず試しました。喜びはありません。

プロセスがハングするのはなぜですか? 修正するにはどうすればよいですか?

PS: 他のいくつかのコマンド ライン アプリでこれを試しましたが、どちらも同じ動作を示します。

さらなる重役の悩み。

標準出力とエラーを読み込もうとしていますが、成功しません。コマンド ラインから、私のコマンド ライン エクスペリエンスと非常によく似たものがあるはずですが、proc.getInputStream() によって返された入力ストリームを読み取ると、すぐに EOL (-1、私は を使用していますinputStream.read()) を取得します。

Process の JavaDoc を確認したところ、これが見つかりました

親プロセスは、これらのストリームを使用して、サブプロセスに入力をフィードし、サブプロセスから出力を取得します。一部のネイティブ プラットフォームでは、標準の入力ストリームと出力ストリームに対して限られたバッファ サイズしか提供されないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りが迅速に行われないと、[b]サブプロセスがブロックされ、さ​​らにはデッドロック[/b]が発生する可能性があります。

強調が追加されました。だから私はそれを試しました。プロセスを強制終了するまで、標準出力 inputStream の最初の「read()」がブロックされました...

WKHTMLTOPDF を使用

一般的なコマンドライン ap & no params を使用すると、「使用状況をダンプして終了」する必要があり、適切な std::out を吸い出してから終了します。

面白い!

JVMのバージョンの問題? 1.6.0_23 を使用しています。最新は... v24です。変更ログを確認したところ、有望なものは何もありませんが、とにかく更新してみます。


わかった。入力ストリームがいっぱいにならないようにしてください。そうしないとブロックされます。小切手。 .close()これを防ぐこともできますが、それほど明るくはありません。

それは一般的に機能します(私がテストした一般的なコマンドラインアプリを含む)。

ただし具体的には落ちる。wkhtmltopdf は、ASCII グラフィック プログレス バーを実行するために端末操作/カーソルを使用しているようです。これにより、正しい値が返されるのではなく、inputStream がすぐに EOF を返すようになっていると思います。

何か案は?契約を破ることはほとんどありませんが、それは間違いなくナイス・オブ・ハヴです.

4

4 に答える 4

11

私はあなたとまったく同じ問題を抱えていて、それを解決しました。ここに私の発見があります:

何らかの理由で、wkhtmltopdf からの出力はプロセスの STDERR に送られ、STDOUT には送られません。Java と perl から wkhtmltopdf を呼び出して、これを確認しました。

したがって、たとえば Java では、次のようにする必要があります。

//ProcessBuilder is the recommended way of creating processes since Java 1.5 
//Runtime.getRuntime().exec() is deprecated. Do not use. 
ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
Process process = pb.start();

BufferedReader errStreamReader = new BufferedReader(new  InputStreamReader(process.getErrorStream())); 
//not "process.getInputStream()" 
String line = errStreamReader.readLine(); 
while(line != null) 
{ 
    System.out.println(line); //or whatever else
    line = reader.readLine(); 
}

余談ですが、Java からプロセスを生成する場合は、stdout および stderr ストリームから読み取る必要があります (何もしない場合でも)。そうしないと、ストリーム バッファーがいっぱいになり、プロセスがハングして戻らないためです。

コードを将来的に保証するために、wkhtmltopdf の開発者が stdout への書き込みを決定した場合に備えて、子プロセスの stderr を stdout にリダイレクトし、次のように 1 つのストリームのみを読み取ることができます。

ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath); 
pb.redirectErrorStream(true); 
Process process = pb.start(); 
BufferedReader inStreamReader = new BufferedReader(new  InputStreamReader(process.getInputStream())); 

実際、Java から外部プロセスを生成する必要があるすべてのケースでこれを行います。そうすれば、2 つのストリームを読み取る必要がなくなります。

ストリームからの読み取りがブロックされているため、メインスレッドをブロックしたくない場合は、生成されたプロセスのストリームを別のスレッドで読み取る必要もあります。

お役に立てれば。

更新:プロジェクト ページでこの問題を提起しましたが、wkhtmltopdf は STDOUT で実際の pdf 出力を提供することをサポートしているため、これは設計によるものであると回答されました。詳細と Java コードについては、リンクを参照してください。

于 2012-01-16T19:08:39.987 に答える
4

プロセスには、入力、出力、エラーの 3 つのストリームがあります。別々のプロセスを使用して、出力とエラー ストリームの両方を同時に読み取ることができます。この質問とその受け入れられた回答、およびたとえばこれも参照してください。

于 2011-03-31T21:10:14.193 に答える
2

別のスレッドのストリームから読み取る必要があります。

于 2011-03-31T21:04:22.977 に答える
2
    final Semaphore semaphore = new Semaphore(numOfThreads);
    final String whktmlExe = tmpwhktmlExePath;
    int doccount = 0;
    try{
        File fileObject = new File(inputDir);
        for(final File f : fileObject.listFiles()) {

            if(f.getAbsolutePath().endsWith(".html")) {
                doccount ++;
                if(doccount >500 ) {
                    LOG.info(" done with conversion of 1000 docs exiting ");
                    break;
                }
                System.out.println(" inside for before "+semaphore.availablePermits());
                semaphore.acquire();
                System.out.println(" inside for after "+semaphore.availablePermits() + " ---" +f.getName());
                new java.lang.Thread() {
                    public void run() {
                        try {
                            String F_ =  f.getName().replaceAll(".html", ".pdf") ;
                            ProcessBuilder pb = new ProcessBuilder(whktmlExe , f.getAbsolutePath(), outPutDir + F_ .replaceAll(" ", "_") );//"wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
                            pb.redirectErrorStream(true);
                            Process process = pb.start();
                            BufferedReader errStreamReader = new BufferedReader(new  InputStreamReader(process.getInputStream()));  
                            String line = errStreamReader.readLine(); 
                            while(line != null) 
                            { 
                                System.err.println(line); //or whatever else
                                line = errStreamReader.readLine(); 
                            }

                            System.out.println("after completion for ");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }finally {
                            System.out.println(" in finally releasing ");
                        semaphore.release();
                        }
                  }
                }.start();
            }
        }
    }catch (Exception ex) {
        LOG.error(" *** Error in pdf generation *** ", ex);
    }

    while (semaphore.availablePermits() < numOfThreads) {//till all threads finish 
        LOG.info( " Waiting for all threads to exit "+ semaphore.availablePermits() + " --- " +( numOfThreads - semaphore.availablePermits()));
        java.lang.Thread.sleep(10000);
    }
于 2012-11-02T12:13:17.400 に答える