OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

C# 外部プロセスとしてnpmコマンドを呼ぶと標準出力の終端で固まる

んー、わからん。

Windows環境で、自作プログラムからNodeのnpmコマンドを呼んで、標準出力内容をリダイレクトして読み取るようなことをやりたくて、C#のコンソールアプリとして作っていたのだけど。

こんな具合に。

static void Main(string[] args)  
{  
    //コマンドプロンプト  
    var cmdPath = Environment.GetEnvironmentVariable("ComSpec");  

    var stInfo = new ProcessStartInfo(cmdPath);  
    stInfo.UseShellExecute = false;  
    stInfo.RedirectStandardOutput = true;  
    stInfo.RedirectStandardInput = false;  
    stInfo.CreateNoWindow = true;  

    //versionを出力させるとする  
    stInfo.Arguments = string.Format("/c npm --version");  

    using (var ps = new Process())  
    {  
        ps.StartInfo = stInfo;  

        //出力をハンドルしてコンソールに出力し直す  
        ps.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);  

        ps.Start();  

        //非同期で読み取り開始  
        ps.BeginOutputReadLine();  

        //終了を待機  
        ps.WaitForExit();  
    }  

    Console.WriteLine("おわり");  
}  

.NET Framework 4.6.2、npm バージョンは5.6.0。

これを実行すると、npmコマンドからのアウトプットはちゃんと出力されるのだけど、最後まで出力されても ps.WaitForExit() で止まったままで、1分くらいたって(タイムアウト?)ようやく終了される。

つまり、出力ストリームで終端を検知できていない感じなのだろうか。

npm以外のコマンドでは、nodeコマンドであれdirコマンドであれ、ちゃんと終端を検知してプロセス終了される。

で、一応解決策はわかった。

ps.WaitForExitの、タイムアウト時間を指定するほうのオーバーロードを使って、適当な数値を渡してやると、ちゃんとプロセスが終了されるようになる。
これが謎で、たとえば ps.WaitForExit(60 * 1000) という具合に60秒とかで指定しても、60秒を待たずに、すぐに終了してくれるようになるのだ。

Reference Source でソースを覗いてみると、どうやらWaitForExitは内部的に user32.dllのWaitForInputIdle関数を呼んでいて、そこにタイムアウト時間も渡しているらしいが、内部まではわからず、そこであきらめた。

とりいそぎご報告です。