C# Process.StandardOutput.Peek not working - c#

I need to use standard input/output on process, so I created simple app "test":
var line = String.Empty;
do
{
Console.Write($"previous input ==> {line}, type next input> ");
line = Console.ReadLine();
}
while (!String.IsNullOrWhiteSpace(line) && line != "quit");
Console.WriteLine("End");
which receives something on standard input and writes on output. Then I created new app which needs to start that app "test" and use standard iput/output like:
var process = new Process
{
EnableRaisingEvents = false,
StartInfo = new ProcessStartInfo
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
Arguments = Arguments,
CreateNoWindow = true,
FileName = Name,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = WorkingDirectory
},
};
process.Start();
String? input;
do
{
Thread.Sleep(10000); // Sleep to be sure that "test" app generated output
var line = String.Empty;
while (process.StandardOutput.Peek() > -1)
line += (char)process.StandardOutput.Read();
Console.Write($"[Standard Output]{line}\t[New Input]");
input = Console.ReadLine();
process.StandardInput.WriteLine(input);
}
while (input != "quit");
The problem is that I get this as output:
[Standard Output]previous input ==> , type next input> [New Input]test
[Standard Output] [New Input]
The "process.StandardOutput.Peek()" second time is returning -1 and there exist output of "test" app. Is it possible to get next what is generated on standard output by "test" app from app that started that process.
I need to get second output generated from "test" app, so I expect to see line:
[Standard Output]previous input ==> test, type next input> [New Input]

If your platform is Windows, try PeekNamedPipe.
static string ReadAvailableString(StreamReader reader)
{
[DllImport("kernel32.dll")]
static extern bool PeekNamedPipe(
SafeFileHandle hNamedPipe,
IntPtr lpBuffer,
int nBufferSize,
IntPtr lpBytesRead,
out int lpTotalBytesAvail,
IntPtr lpBytesLeftThisMessage
);
var stream = (FileStream)reader.BaseStream;
if( !PeekNamedPipe(stream.SafeFileHandle, IntPtr.Zero, 0, IntPtr.Zero, out var totalbytesAvail, IntPtr.Zero) || totalbytesAvail<=0 )
return String.Empty;
Span<byte> buf = stackalloc byte[totalbytesAvail];
stream.Read(buf);
return reader.CurrentEncoding.GetString(buf);
}
Here is an example of getting StandardOutput.
var output = ReadAvailableString(process.StandardOutput);

Problem is Peek is non-blocking call which does not wait for data to become available. You start new process and then immediately proceed checking its standard output with Peek, but it might be nothing there yet. This is what you observe. Instead - you should read until some stopping point, but in this case there is no such point, so you can introduce it - use Console.WriteLine instead of Console.Write here:
Console.WriteLine($"previous input ==> {line}, type next input> ");
Now on receiving end you can read until you meet newline character:
line = process.StandardOutput.ReadLine();
Note that this is blocking read. It will read until newline, and if data is not available yet - it will wait until it's there. Now "messages" in your communication have clear boundaries.
I would even say that you should forget that Peek() exist and never use it. I've never used it in my practice and all usages I ever saw lead to bugs like this.

After trying a lot of things it seams that Process.StandardOutput.Peek is not working. You can use the "PeekNamedPipe" (answer from radian) for Windows OS.
I managed to work it on Windows and Linux using CliWrap (https://github.com/Tyrrrz/CliWrap). The problem here is that for input stream you need Stream that has blocking read() method, so I creted/implemented one for me. I will not put here the implementation of that stream, you can use any stream that satisfy that condition. So, here is the final version of above example using CliWrap library
var stdOutBuffer = new StringBuilder();
var stdErrBuffer = new StringBuilder();
// The CliStream is my own implementation of Stream class.
// The CliWrap library is calling method: int Read(byte[] buffer, int offset, int count)
// with parameters offset = 0 and count = 131072
// it is important that this method is blocking if nothing is in stream (it done it using Semaphores)
// if it is not blocking then you will have some unexpected behaviour
var stream = new CliStream();
var cmd = CliWrap.Cli.Wrap(ExecName)
.WithArguments(Arguments)
.WithStandardErrorPipe(CliWrap.PipeTarget.ToStringBuilder(stdErrBuffer))
.WithStandardOutputPipe(CliWrap.PipeTarget.ToStringBuilder(stdOutBuffer))
.WithStandardInputPipe(CliWrap.PipeSource.FromStream(stream))
.WithWorkingDirectory(WorkingDirectory);
cmd.ExecuteAsync();
String? input;
do
{
Thread.Sleep(10000); // Sleep to be sure that "test" app generated output
Console.Write($"[Standard Output]{stdOutBuffer}\t[New Input]");
stdOutBuffer.Clear();
input = Console.ReadLine();
var buffer = Encoding.UTF8.GetBytes(input + Environment.NewLine);
stream.Write(buffer);
}
while (input != "quit");
Thanks to everyone for contributing.

Related

Process.WaitForExitAsync stucks infinitely while calling ffmpeg on Windows

I am working with ffmpeg via C#'s Process class.
I have a script that runs ffmpeg to generate thumbnails from video. Initially it was called manually from command line - .\ffmpeg.exe -i .\input.mp4 -ss 00:00:01.000 -vframes:v 1 output.png, it starts ffmpeg instance, outputs some warnings/errors during the execution:
[image2 # 000001e51095ec80] The specified filename 'output.png' does not contain an image sequence pattern or a pattern is invalid.
[image2 # 000001e51095ec80] Use a pattern such as %03d for an image sequence or use the -update option (with -frames:v 1 if needed) to write a single image.
frame= 1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.00 bitrate=N/A speed= 0x
video:73kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
but anyway exits the process and correctly generates thumbnail image at output.png.
I want to execute it from C#.
Let's see the code:
var ffmpegProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = _config.FfmpegExecutablePath,
Arguments = CreateArgumentsForFfmpegProcessToRun(videoTempFilePath, thumbnailTempFilePath),
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
},
EnableRaisingEvents = true
};
ffmpegProcess.Start();
await ffmpegProcess.WaitForExitAsync();
Method CreateArgumentsForFfmpegProcessToRun returns exactly the same arguments as in the script above -i .\input.mp4 -ss 00:00:01.000 -vframes:v 1 output.png.
However, when I run this code, it stucks/blocked at line ffmpegProcess.WaitForExitAsync() infinitely and no output written to output path.
If I omit WaitForExitAsync call and just go to the next line, then it doesn't stuck and writes the output as expected and finish the process with -1 exit code.
I am trying to figure out why block/stuck happens and what is the best way to resolve this situation? As far I know, WaitForExitAsync should return as process ends, no matter how process ends - 0 or another exit code, am I right?
Update #1:
Community advised to search if somewhere up the stack I am blocking my code. I wrote xunit-test and it still stucks.
[Theory]
[InlineData("assets/input.mp4", "assets/ffmpeg.exe")]
public async Task CreateThumbnailFromVideo(string videoFilePath, string ffmpegExePath)
{
var config = new VideoThumbnailServiceConfig
{
FfmpegExecutablePath = ffmpegExePath,
ThumbnailImageExtension = ".png"
};
var sut = new VideoThumbnailService(config);
using var fileStream = File.OpenRead(videoFilePath);
await sut.CreateThumbnailFromVideo(fileStream);
}
Inside sut.CreateThumbnailFromVideom I call process start method and awaits WaitForExitAsync().

How to avoid avoid broken pipes using c# and Python?

When trying to use stdin and stdout in C# (Unity) to pipe to a Python process, I get about a dozen or so transactions and the process breaks and the error "ObjectDisposedException: The object was used after being disposed."
After trying several of the more obvious things, I'm bringing the problem here perhaps someone know just the right technique. Thanks in advance.
Here's the C# Startup code:
Process pyProcess; // <=== fixed
ProcessStartInfo pyStartInfo;
public StreamReader pyStreamReader;
public StreamWriter pyStreamWriter;
public void startPython()
{
// Create new process start info
pyStartInfo = new ProcessStartInfo(pyPath)
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
Arguments = pyApp + " " + pyArgs
};
pyProcess = new Process { StartInfo = pyStartInfo };
pyProcess.Start();
pyStreamReader = pyProcess.StandardOutput;
pyStreamWriter = pyProcess.StandardInput;
pyStreamWriter.WriteLine("Hello!");
string str = pyStreamReader.ReadLine();
Debug.LogFormat(str + "\n");
}
void Start()
{
if(testPython == true)
startPython();
Here is the fragment that generates data sent to python at each update...
if (controller.testPython)
{
string str, python;
str = String.Format("data to send");
pyStreamWriter.DiscardBufferedData(); #<==== fixed
pyStreamWriter.WriteLine(str);
python = pyStreamReader.ReadLine();
Debug.LogFormat("python says: " + python + "\n");
}
And here is the simplified python process that's echoing the data
while True:
cmd = input() # read a command from c#
print(cmd) # process the cmd, here we just echo it back to c#
After a little experimentation, I discovered that adding
pyStreamReader.DiscardBufferedData();
before
pyStreamWriter.WriteLine(str);
solves the main problem and this simple form of piping seems to work, at least for hundreds of transactions that I observed.
I also had to declare pyProcess outside the scope so the code so that its handle is not released. That resolved the ObjectDisposed exception.

Start Process from Service hung UseShellExecute false

I run into a strange thing and can't find the reason why this is happening.
I have a service.exe where I collect data from configuration registry and then start n processes.
Sample Code:
_mProcess.StartInfo = new ProcessStartInfo
{
FileName = Command,
Arguments = Argument,
WorkingDirectory = WorkDir
};
_mProcess.Start();
Pid = _mProcess.Id;
My Pid contains the process id.
Now I added UseShellExecute = false to get the StandardOutput.
New sample code:
_mProcess.StartInfo = new ProcessStartInfo
{
FileName = Command,
Arguments = Argument,
WorkingDirectory = WorkDir //,
//CreateNoWindow = true,
UseShellExecute = false,
//RedirectStandardOutput = true,
RedirectStandardError = true
//RedirectStandardInput = true
};
_mProcess.Start();
Pid = _mProcess.Id;
using (var reader = _mProcess.StandardError)
{
_logger.ToLog("", Company, reader.ReadToEnd(), "RCluster.log", "service");
}
In this case the process return back any error which I can store to my log file.
Problem: This code is part of a method to start a process which I call many times (depends on my configuration).
So with this code the first process is called, the following processes are not.
Somehow the service wait for the first service now. I thought this happens only with WaitForExit.
So how I can get standard error output but not make the process block my main task to continue?
#Gusman: Add you comment as answer, you brought me to the correct answer. Then I can accept your comment as answer.
To the down-voter: Explanation why would be appreciated.
To all: I added some code to start the new process as a thread. In this case it make sense to start it in another thread to grab `StandardError messages but do not block the main process (which is a service in my case that starts many sub processes).
// start as new thread to prevent blocking
var ths = new ThreadStart(() =>
{
mProcess.Start();
Pid = mProcess.Id;
// write pid file
File.WriteAllText(RubyDir + #"\tmp\pids\" + Port + #".pid", Pid.ToString());
using (var reader = mProcess.StandardError)
{
var errorMsg = reader.ReadToEnd();
if (errorMsg.Length > 0) _logger.ToLog("", Company, errorMsg, "SOLR.log", "service");
}
});
var th = new Thread(ths);
th.Start();

Why can't I get the output of a C++ program using Process in C#?

I've got a C++ program which uses wprintf_s function to print the results into the command line. But when I uses Process in C# to read the output of the program, I cannot get any words of it. However when I added a fflush(stdout) after the wprintf_s statement, I can finally read the standard output in my C# program.
The code I use to start the progress is:
var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "FILENAME",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
StringCollection values = new StringCollection();
proc.OutputDataReceived += (s, args) => {
lock (values) {
values.Add(args.Data);
}
};
proc.BeginOutputReadLine();
proc.WaitForExit();
Can anybody tell me why a fflush(stdout) would work?
The output is being buffered in the C++ process and will remain so until the buffer is full or it is flushed, e.g. by calling fflush(), by closing the stream, or other OS dependant reasons.
fflush() just causes any data in the output buffer to be written to the stream.
If you don't want to explicitly call fflush(), you can consider setting unbuffered mode on the output stream by calling setbuf() with a NULL pointer for the second argument:
#include <stdio.h>
#include <unistd.h>
int main()
{
setbuf(stdout, (char *)NULL);
while (1)
{
fputs("hi there\n", stdout);
sleep(1);
}
}
Now the output will appear immediately.
Note that if stdout is a terminal, setbuf(f, NULL) is unnecessary as this is the default behaviour for terminal devices. If stdout is a pipe, then setbuf(f, NULL) will make it unbuffered.

Execute a command line utility in ASP.NET

I need some advice regarding the use of a command line utility from a C#/ASP.NET web application.
I found a 3rd party utility for converting files to CSV format. The utility works perfectly and it can be used from the command line.
I have been looking on the web for examples on how to execute the command line utility and found this example.
The problem is this is not very good. When I try to us the example code with my utility, I get a prompt asking me to install the utility on the client machine. This is not what I want. I do not want the user to see what is going on in the background.
Is it possible to execute the command server side and processing the file from there?
Any help would be greatly appreciated.
I've done something like this several times in the past, and here's what's worked for me:
Create an IHttpHandler implementation (easiest to do as an .ashx file) to handle a convert. Within the handler, use System.Diagnostics.Process and ProcessStartInfo to run your command line utility. You should be able to redirect the standard output to the output stream of your HTTP response. Here's some code:
public class ConvertHandler : IHttpHandler
{
#region IHttpHandler Members
bool IHttpHandler.IsReusable
{
get { return false; }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
var jobID = Guid.NewGuid();
// retrieve the posted csv file
var csvFile = context.Request.Files["csv"];
// save the file to disk so the CMD line util can access it
var filePath = Path.Combine("csv", String.Format("{0:n}.csv", jobID));
csvFile.SaveAs(filePath);
var psi = new ProcessStartInfo("mycsvutil.exe", String.Format("-file {0}", filePath))
{
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = new Process { StartInfo = psi })
{
// delegate for writing the process output to the response output
Action<Object, DataReceivedEventArgs> dataReceived = ((sender, e) =>
{
if (e.Data != null) // sometimes a random event is received with null data, not sure why - I prefer to leave it out
{
context.Response.Write(e.Data);
context.Response.Write(Environment.NewLine);
context.Response.Flush();
}
});
process.OutputDataReceived += new DataReceivedEventHandler(dataReceived);
process.ErrorDataReceived += new DataReceivedEventHandler(dataReceived);
// use text/plain so line breaks and any other whitespace formatting is preserved
context.Response.ContentType = "text/plain";
// start the process and start reading the standard and error outputs
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// wait for the process to exit
process.WaitForExit();
// an exit code other than 0 generally means an error
if (process.ExitCode != 0)
{
context.Response.StatusCode = 500;
}
}
}
#endregion
}
The command is running server side. Any code is running on the server. The code in the example that you give works. You just need to make sure that the utility is set up properly on the server and that you have permissions to the directory/file.

Categories