I'm using C# and Mono 2.10.2 on Debian 6.
So the scenario is that I created a process with Process.Start() like the following:
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.WorkingDirectory = "/home/lucy/";
p.StartInfo.FileName = "/bin/sh";
p.StartInfo.Arguments = "/home/lucy/test.sh";
p.EnableRaisingEvents = true;
p.ErrorDataReceived += new DataReceivedEventHandler(ShellProc_ErrorDataReceived);
p.Start();
The shell script which is in this case called test.sh is ran which does several things including starting a java application. The problem I am recieving is when the c# application is terminated the bash script/java application also terminates.
I have looked at several other similar questions posted here on Stack Overflow and none come to an obvious conclusion, including this one:
How to create a Process that outlives its parent
According to some users and supposedly the docs, processes created by Process.Start() should not be terminated when the application terminates, but obviously in my case that is not true. So could this be a Mono related issue and if that is indeed the case then is there any alternatives to how I'm doing it now as I am out of ideas.
Here is a complete sample that works for me:
using System;
using System.Diagnostics;
class Tick {
static void Main(string[] args) {
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
p.StartInfo.FileName = "/bin/sh";
p.StartInfo.Arguments = "test.sh";
p.EnableRaisingEvents = true;
p.ErrorDataReceived += new DataReceivedEventHandle(ShellProc_ErrorDataReceived);
p.Start();
System.Threading.Thread.Sleep (5000);
Console.WriteLine ("done");
}
static void ShellProc_ErrorDataReceived (object sender, DataReceivedEventArgs ea)
{
}
}
and then test.sh is:
while true; do
date;
sleep 1;
done
When I run the sample from a terminal, the test.sh script will continue to output data after the sample program has exited.
Update 1/Solution: This actually was not mono's fault and was indeed my own fault, the answer below helped me come to the conclusion that it was something else in my application that was causing the processes started by the application to terminate when the application terminates and the actual thing that was causing this was some GC stuff, specifically GC.Collect(), my fault, sorry and I hope this helps anybody who has a similar problem.
Related
Let's say we start console application with:
public static void StartProcess()
{
using var next = new Process();
next.StartInfo.UseShellExecute = false;
next.StartInfo.FileName = "dotnet";
next.StartInfo.Arguments = "/opt/ConsoleApp1/ConsoleApp1.dll";
next.Start();
}
This code leads to double StandardOutput and StandardError, because parent and child processes will write data to the same terminal. How to supress child process output and/or detach child console?
Of course I can do something like this:
public static void StartProcess()
{
using var next = new Process();
next.StartInfo.UseShellExecute = false;
next.StartInfo.FileName = "dotnet";
next.StartInfo.Arguments = "/opt/ConsoleApp1/ConsoleApp1.dll";
next.StartInfo.RedirectStandardOutput = true;
next.StartInfo.RedirectStandardError = true;
next.Start();
next.StandardOutput.BaseStream.CopyToAsync(Stream.Null);
next.StandardError.BaseStream.CopyToAsync(Stream.Null);
}
As far as I understand this will work until parent process is alive, but what if child process will work longer than parent one? Need some stable, cross-platform solution.
Adding StartInfo.CreateNoWindow = true fixed it for me.
Well, almost... Starting the process still spits out a newline onto the Linux console, but no other output.
I read many posts here with a similar question, but none helped me solve my problem.
I want to stop the service with the ServiceController object. But it fails and I get an exception: System.ComponentModel.Win32Exception (0x80004005). I don't understand why.. I run the program with "Run as administrator".
ServiceController ctrl = ServiceController.GetServices().Where(s => s.ServiceName == "service_name").SingleOrDefault();
if (ctrl == null) return;
if (ctrl.Status.Equals(ServiceControllerStatus.Running))
{
try
{
ctrl.Stop();
}
catch(Exception ex)
{
Log(ex.ToString(), 3);
}
}
If I call the net stop command from the code, then everything works. Why?
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
cmd.StandardInput.WriteLine("net stop service_name");
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
cmd.WaitForExit();
After our continued chat discussion ServiceController following was discovered :
ServiceController was able to Start the service, but not Stop
From the System Logs it was evident that ServiceController crashes when trying to stop
ServiceControllers Start & Stop works fine with other services such as PrintSpooler
Using CMD or Process.Start we can stop service by sc stop "servicename"
Finally the problem was with the Service itself and the way it was constructed
I need to communicate with external executable (ampl.exe) using standard input and standard output. This exe make calculations during some minutes with some display in the console. It has a prompt so I can succesively launch calculations by using its standard input as soon as a calculation is finished.
The external exe is launched as :
var myProcess = new Process();
myProcess.StartInfo = new ProcessStartInfo("ampl.exe");
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.StartInfo.RedirectStandardInput = true;
myProcess.Start();
I communicate with it by using myProcess.StandardInput and myProcess.StandardOutput (synchronous way).
I use standard input to launch the calcul, for example :
myProcess.StandardInput.WriteLine("solve;");
I want to wait the end of the solve statement, get results in files, prepare new calculation input files and then launching a second solve.
My problem is that I do now know when the first calculation is finished, that is when the exe is waiting for new command in its standard input.
The only way I found is to add a specific display command and wait for getting it it its standard output :
myProcess.StandardInput.WriteLine("solve;");
myProcess.StandardInput.WriteLine("print 'calculDone';");
string output = myProcess.StandardOutput.ReadLine();
while (!output.Contains("calculDone"))
{
output = myProcess.StandardOutput.ReadLine();
}
Is there another way avoiding to use this display command to do this ?
Edit : following advices, I tried the asynchronous way. But I still need to print 'CalculDone' to know when the solve statement ended. I do not get the prompt of ampl.exe (which is 'ampl : ') in the standard output of the process.
AutoResetEvent eventEnd = new AutoResetEvent(false);
var myProcess = new Process();
myProcess.StartInfo = new ProcessStartInfo("ampl.exe");
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.StartInfo.RedirectStandardInput = true;
myProcess.EnableRaisingEvents = true;
myProcess.OutputDataReceived += (sender, e) =>
{
if (e.Data == "commandDone")
{
eventEnd.Set();
}
else if (e.Data != null)
{
Console.WriteLine("ampl: {0}", e.Data);
}
};
myProcess.Start();
myProcess.BeginOutputReadLine();
myProcess.StandardInput.WriteLine("solve;");
myProcess.StandardInput.WriteLine("print 'commandDone';");
eventEnd.WaitOne();
The best option would be to use the Processs.OutputDataReceived event instead of a tight while loop. It’s like the event async pattern, you launch an asynchronous task and wait for an event callback telling you it’s done. The continuation of the asynchronous task would go in the event handler. Remember to unsubscribe the event handler the first time it goes off, otherwise it will be firing when you don’t want it to.
Another option I have never tried is Process.WaitForInputIdle() method, but I’m not sure if this will work in your particular case. If it does you wouldn’t need to write anything to the input stream.
I've built Form App that I use for some time , Now I want to Catch the StandardError of my process as well as its standartOutput
I've looked at answers in SO and MSDN and yet and cant get it right
My code :
public void RunProcess(string FileName, string Arguments,, bool IsPrintOutput = true)
{
process = new Process();
process.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceivedEvent);
if (IsPrintOutput) process.OutputDataReceived += new DataReceivedEventHandler(OnDataReceivedEvent);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = FileName;
process.StartInfo.Arguments = Arguments;
if (EventWhenExit)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(myprocess_Exited);
}
process.Start();
process.BeginOutputReadLine();
//run polling on stored logs to print them to screen
PollingService();
}
I've check it with Iperf and I see that when I run it with correct argument I get correct output
but when I just send it with out any argumnet I see that with cmd I get
C:\>iperf.exe
Usage: iperf [-s|-c host] [options]
Try `iperf --help' for more information.
And my App I get Nothing !
what am I missing here ?
Thanks
You can stop reading here ! If you want to see the details of inner method continue below :
private void OnDataReceivedEvent(object sender, DataReceivedEventArgs e)
{
string ProcessOutput = e.Data;
ProcessLog.Add(e.Data);
}
private void PollingService()
{
var T = new Thread (()=>
{
while (true /* ProcessRunning*/)
{
if (ProcessLogIndex < ProcessLog.Count)
{
lock (this)
{
var tempList = ProcessLog.GetRange(ProcessLogIndex, ProcessLog.Count - ProcessLogIndex);
ProcessLogIndex = ProcessLog.Count;
foreach (var ToSend in tempList)
{
onDataOutputFromProcess(this, ToSend, sProcessNameID.ToString());
}
}
}
Thread.Sleep(400);
}
});
T.IsBackground = true;
T.Start();
}
I don't see a call to BeginErrorReadLine() anywhere in the code you posted. If you don't call that method, then the Process class won't actually redirect the stderr to your event handler.
I believe the above is the issue, but if you are actually calling that somewhere (and just didn't show it), then it is worth considering that there are some strange console programs out there that don't actually used stderr (or stdout) for error output. Instead, they write directly to the console window or some other non-standard mechanism. In those cases, you won't be able to receive the error output by redirecting stderr.
You can identify those programs by redirecting their output at the command like with e.g. iperf.exe 2> foo.txt. The stderr file handle is 2, and so that syntax redirects that file handle to a file named foo.txt. If the file is empty and you see errors on the screen, then the program is one of those strange programs.
But really, I think you probably just forgot to call BeginErrorReadLine(). :)
A legacy program "LegacyBuilder" runs "batch.cmd" file and then redirects its output to a file, like this.
ProcessStartInfo processStartInfo = new ProcessStartInfo("C:\\batch.cmd");
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.UseShellExecute = false;
using (Process process = Process.Start(processStartInfo))
{
using (StreamWriter logWriter = new StreamWriter("C:\\log.txt"))
{
while ((logLine = process.StandardOutput.ReadLine()) != null)
{
logWriter.WriteLine(logLine);
}
}
}
The batch.cmd contains this line
C:\SomeApp.exe "arg1" "arg2" "arg3"
SomeApp.exe is using the following method from a different assembly.
SomeAssembly.SomeClass.GenerateOutput()
This GenerateOutput method create a process to a 3rd party console program. After troubles with buffer deadlocking, I discovered (from another SO question I think) the following code never cause such deadlock and output of the 3rd party program is captured by the "LegacyBuilder".
Console.Write("I'm creating the process!");
ProcessStartInfo processStartInfo = new ProcessStartInfo("C:\\3rdPartyConsoleExe.exe");
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.UseShellExecute = false;
using (Process process = new Process())
{
process.StartInfo = processStartInfo;
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (sender, e) =>
{
if (e.Data == null) outputWaitHandle.Set();
else output.AppendLine(e.Data);
};
process.ErrorDataReceived += (sender, e) =>
{
if (e.Data == null) errorWaitHandle.Set();
else error.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (process.WaitForExit(60000) && outputWaitHandle.WaitOne(60000) && errorWaitHandle.WaitOne(60000))
{
Console.WriteLine(output.ToString());
Console.WriteLine(error.ToString());
if (process.ExitCode > 0)
Console.WriteLine("PROCESS COMPLETED ExitCode=" + process.ExitCode.ToString());
}
else
Console.WriteLine("PROCESS TIMED OUT");
}
}
Please note the Console.Write("I'm creating the process!");
So far so good, everything is working, all output is being captured by the "LegacyBuilder"
Now, batch.cmd was updated with calling a powershell script.
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe -File "C:\powershellscript.ps1"
The powershell script basically loads the SomeAssembly, creates SomeObject and then calls the GenerateOutput method. Like this.
Add-Type -Path "C:\SomeAssembly.dll";
$someCls = New-Object SomeAssembly.SomeClass()
$someCls.GenerateOutput();
The problem: The text "I'm creating the process!" is being captured by the "LegacyBuilder" so this is going to stdout.
But the expected output of the GenerateOutput() method is not generating anything and after several attempted calls it gets deadlocked again. Not even "PROCESS TIMED OUT" gets called.
This is weird.
batch.cmd calls "SomeApp.exe" which calls SomeAssembly.SomeClass.GenerateOutput(). The output of the 3rd party program is present in the file and the batch.cmd successfully continues.
batch.cmd calls powershell which loads a script which create SomeAssembly.SomeClass object and then call GenerateOutput() method. The output is not present, only output from direct Console.Write calls is ... after several attemps, it gets deadlocked and batch.cmd never continues.
Any help? I wish something like "StopBufferDeadlocksInPowershell -includeStupidLegacyNestedProcessCalls" existed...
EDIT 1
I would love to avoid making any changes in "LegacyBuilder" since the code in it is really, really complex (the topmost code sample is just simplification) and while I'd love to rewrite it completely, it would be very time consuming.
After days of headaches, somehow, this made it working.
$someCls.GenerateOutput() | Out-Host;
The buffer is no longer deadlocked.