Reputation: 3330
I'm trying to create a pipeline between several Process objects.
I can run a single Process and capture its StandardOutput, but when I try to connect multiple Process objects, the second one doesn't seem to receive any data from the first one's StandardInput.
In this code, I'm using cat
, which either prints the contents of its arguments to stdout, or just copies stdin to stdout.
Using one process works fine:
// Works
private async void launchButtonClicked(object sender, EventArgs e)
{
var outputBuffer = new MemoryStream();
var tasks = new List<Task>();
Process process1;
{
process1 = new Process();
process1.StartInfo.FileName = "cat.exe";
process1.StartInfo.Arguments = "K:\\temp\\streams.txt";
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.RedirectStandardInput = false;
process1.StartInfo.CreateNoWindow = true;
process1.EnableRaisingEvents = true;
process1.Start();
}
tasks.Add(process1.StandardOutput.BaseStream.CopyToAsync(outputBuffer));
await Task.WhenAll(tasks);
// OK: This prints the contents of the file
Console.WriteLine("Final output: {0}", UTF8Encoding.UTF8.GetString(outputBuffer.GetBuffer()));
}
But when I add a second process and try to copy Process1's StandardOutput to Process2's StandardInput, I don't ever get any output from Process2 and the await never completes:
// Doesn't work
private async void launchButtonClicked(object sender, EventArgs e)
{
var outputBuffer = new MemoryStream();
var tasks = new List<Task>();
Process process1, process2;
{
process1 = new Process();
process1.StartInfo.FileName = "cat.exe";
process1.StartInfo.Arguments = "K:\\temp\\streams.txt";
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.RedirectStandardInput = false;
process1.StartInfo.CreateNoWindow = true;
process1.Start();
}
{
process2 = new Process();
process2.StartInfo.FileName = "cat.exe";
process2.StartInfo.Arguments = "";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.RedirectStandardOutput = true;
process2.StartInfo.RedirectStandardInput = true;
process2.StartInfo.CreateNoWindow = true;
process2.Start();
}
tasks.Add(process1.StandardOutput.BaseStream.CopyToAsync(process2.StandardInput.BaseStream));
tasks.Add(process2.StandardOutput.BaseStream.CopyToAsync(outputBuffer));
await Task.WhenAll(tasks); // Never returns!
Console.WriteLine("Final output: {0}", UTF8Encoding.UTF8.GetString(outputBuffer.GetBuffer()));
}
Doing something like this works fine from the command line:
C:\>cat.exe K:\temp\streams.txt | cat.exe
... contents of file ...
C:\>
I tried adding Exited event handlers to these Process objects. Process1 exits fine, but Process2 never exits.
I tried some other commands too (like sed), but Process2 still never seems to do anything.
I'm using the streams' BaseStream properties, because I will eventually be working with binary data. (https://stackoverflow.com/a/4535927/339378) This also means I can't use OutputDataReceived
, which returns a string (and that would be more complicated anyway).
Thanks!
Upvotes: 2
Views: 755
Reputation: 3330
Apparently, CopyToAsync
doesn't close the output stream on completion. This didn't matter for my MemoryStream, but obviously I need to close a process's StandardInput or it won't ever complete.
I made this method:
private static async Task CopyThenClose(Stream from, Stream to)
{
await from.CopyToAsync(to);
to.Close();
}
And replaced the calls to CopyToAsync
; eg.:
tasks.Add(CopyThenClose(process1.StandardOutput.BaseStream, process2.StandardInput.BaseStream));
Upvotes: 3