StandardOutput stream staying open after process exits - c#

When working with processes in .NET, I have become accustomed to the behavior that the exiting of the process will end the output of StandardOutput and StandardError. However, I have found one exe where this does not seem to be the case, and I would like to understand whether and why my assumption is wrong.
Here is code that reproduces the issue I am seeing. It uses a specific version of the Android Debug Bridge executable (available from https://hostr.co/bBxxj7gFfMlW).
// before running, terminate any adb.exe processes on the system
// adb.exe's initial run leaves behind a background daemon that changes the
// behavior of future adb.exe runs
var process = new Process
{
StartInfo =
{
FileName = #"C:\Users\[my user]\Downloads\ADB-1.0.31.zip\adb.exe",
Arguments = "connect 127.0.0.1",
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardInput = false,
RedirectStandardOutput = true,
UseShellExecute = false,
}
};
process.Start();
var tasks = new Task[]
{
Task.Run(() => { process.WaitForExit(); Console.WriteLine("Process exited"); }),
Task.Run(() =>
{
int b;
while ((b = process.StandardOutput.BaseStream.ReadByte()) != -1)
{
Console.WriteLine($"STDOUT: {b} ({(char)b})");
}
Console.WriteLine("STDOUT: DONE");
}),
Task.Run(() =>
{
int b;
while ((b = process.StandardError.BaseStream.ReadByte()) != -1)
{
Console.WriteLine($"STDERR: {b} ({(char)b})");
}
Console.WriteLine("STDERR: DONE");
})
};
Task.WaitAll(tasks);
I would expect to see that the process runs for a bit, produces some output (logged byte-by-byte to console), and then exits. Once the process exits, the tasks that are reading from the stream should get -1 back and exit themselves.
All of this happens, EXCEPT that after the process exits the streams stay open and the reader tasks hang indefinitely on ReadByte().
EDIT:
Did a bit more digging and found some code in the implementation of Process that underscores my expectation that the stream should close when the process dies. In WaitForExit(), if you are asynchronously streaming output using BeginOutputDataReadLine(), the wait call blocks on making sure that the async output readers received EOF (code). If the above example is modified to use:
process.OutputDataReceived += (o, e) => Console.WriteLine(e.Data);
process.BeginOutputReadLine();
instead of having an explicit task that reads bytes from StandardOutput, then the WaitForExit() task never returns (although polling process.HasExited returns false after a while)!

Related

How can I reliably read the full output of a process in c# when providing a max wait time to WaitForExit()? [duplicate]

This question already has answers here:
How to read to end process output asynchronously in C#?
(4 answers)
Closed 1 year ago.
Consider the following program. Here I start a simple process and want to deal with it's output. I assumed that would be the case after WaitForExit has returned, but it turns out, that I have to wait upto a full second until that output actually arrives in my program.
static void Main(string[] args)
{
using var p = new Process();
p.StartInfo.FileName = "echo";
p.StartInfo.Arguments = "I apologize for being late";
p.StartInfo.CreateNoWindow = false;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
var stdError = new StringBuilder();
var stdOutput = new StringBuilder();
p.ErrorDataReceived += (sender, args) => stdError.AppendLine(args.Data);
p.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data);
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
// without the int-parameter here, it works
while (!p.WaitForExit(10000))
Console.WriteLine("still waiting...");
string a = stdOutput.ToString();
string b = stdError.ToString();
Thread.Sleep(1000);
string c = stdOutput.ToString();
string d = stdError.ToString();
Console.WriteLine("output before sleep: " + a);
Console.WriteLine("error before sleep: " + b);
Console.WriteLine("output after sleep: " + c);
Console.WriteLine("error after sleep: " + d);
}
output
output before sleep:
error before sleep:
output after sleep: I apologize for being late
error after sleep:
Here I would expect, that a and c have the exact same value. But that is not the case. How would I modify this example such that I reliable receive the full output of the process, but without calling Thread.Sleep(1000)?
Notes:
I want the reliable complete output of both stdOut and stdErr
when I use p.WaitForExit() instead p.WaitForExit(10000) everything seems to work
When using p.StandardOutput.ReadToEnd() for both streams it seems to work. But I am told by the official documentation, that this would lead to deadlocks
when using p.StandardError.ReadToEnd() while using the async solution for the output, than the output still arrives late.
this is not a duplicate of Process WaitForExit not waiting because for them p.WaitForExit()without any parameter already doesn't work. Also they are not interested in the output at all.
There is an awkward implementation detail that is at play here.
Calling
p.WaitForExit();
and
p.WaitForExit(10000);
do slightly different things when the actual native processhandle gets signaled.
Internally p.WaitForExit(); calls p.WaitForExit(-1);. The -1 is significant here. Let's see what we have (code is simplified/ paraphrased to show the essence):
public bool WaitForExit(int milliseconds)
{
// init stuff removed
bool flag;
try
{
flag = processWaitHandle.WaitOne(milliseconds, false);
}
finally
{
// here we see our -1 return
if (this.output != null && milliseconds == -1)
{
this.output.WaitUtilEOF();
}
}
return flag;
}
In the above snippet you see this.output.WaitUtilEOF(); and that calls into an internal AsyncStreamReader that employs a queue. The call to WaitUtilEOF(); basically waits on the stream for the EOF event to be raised.
There is no other way that I could find to force the Process class to make a call to wait for those EOF events. The only option is to call WaitForExit() without a parameter. There is however no penalty in calling WaitForExit(); after a call to WaitForExit(10000) returned.
So if your timeout on the first WaitForExit(10000) was reached but you're sure you rather wait a bit longer for the AsyncStreamReader to hand you all data it has, call WaitForExit() without a parameter to have both AsyncStreamReaders empty their queue and then return control to you. This does mean that if your process didn't end you're now stuck in a wait that won't ever resolve itself unless you kill the child process by yourself.

Process sometimes hangs while waiting for Exit

What may be the reason of my process hanging while waiting for exit?
This code has to start powershell script which inside performs many action e.g start recompiling code via MSBuild, but probably the problem is that it generates too much output and this code gets stuck while waiting to exit even after power shell script executed correctly
it's kinda "weird" because sometimes this code works fine and sometimes it just gets stuck.
Code hangs at:
process.WaitForExit(ProcessTimeOutMiliseconds);
Powershell script executes in like 1-2sec meanwhile timeout is 19sec.
public static (bool Success, string Logs) ExecuteScript(string path, int ProcessTimeOutMiliseconds, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var outputWaitHandle = new AutoResetEvent(false))
using (var errorWaitHandle = new AutoResetEvent(false))
{
try
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
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();
process.WaitForExit(ProcessTimeOutMiliseconds);
var logs = output + Environment.NewLine + error;
return process.ExitCode == 0 ? (true, logs) : (false, logs);
}
}
finally
{
outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
}
}
}
Script:
start-process $args[0] App.csproj -Wait -NoNewWindow
[string]$sourceDirectory = "\bin\Debug\*"
[int]$count = (dir $sourceDirectory | measure).Count;
If ($count -eq 0)
{
exit 1;
}
Else
{
exit 0;
}
where
$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
Edit
To #ingen's solution I added small wrapper which retries to execute hanged up MS Build
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
var current = 0;
int attempts_count = 5;
bool _local_success = false;
string _local_logs = "";
while (attempts_count > 0 && _local_success == false)
{
Console.WriteLine($"Attempt: {++current}");
InternalExecuteScript(path, processTimeOutMilliseconds, out _local_logs, out _local_success, args);
attempts_count--;
}
success = _local_success;
logs = _local_logs;
}
Where InternalExecuteScript is ingen's code
Let's start with a recap of the accepted answer in a related post.
The problem is that if you redirect StandardOutput and/or StandardError the internal buffer can become full. Whatever order you use, there can be a problem:
If you wait for the process to exit before reading StandardOutput the process can block trying to write to it, so the process never ends.
If you read from StandardOutput using ReadToEnd then your process can block if the process never closes StandardOutput (for example if it never terminates, or if it is blocked writing to StandardError).
Even the accepted answer, however, struggles with the order of execution in certain cases.
EDIT: See answers below for how avoid an ObjectDisposedException if the timeout occurs.
It's in these kind of situations, where you want to orchestrate several events, that Rx really shines.
Note the .NET implementation of Rx is available as the System.Reactive NuGet package.
Let's dive in to see how Rx facilitates working with events.
// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
FromEventPattern allows us to map distinct occurrences of an event to a unified stream (aka observable). This allows us to handle the events in a pipeline (with LINQ-like semantics). The Subscribe overload used here is provided with an Action<EventPattern<...>> and an Action<Exception>. Whenever the observed event is raised, its sender and args will be wrapped by EventPattern and pushed through the Action<EventPattern<...>>. When an exception is raised in the pipeline, Action<Exception> is used.
One of the drawbacks of the Event pattern, clearly illustrated in this use case (and by all the workarounds in the referenced post), is that it's not apparent when / where to unsubscribe the event handlers.
With Rx we get back an IDisposable when we make a subscription. When we dispose of it, we effectively end the subscription. With the addition of the DisposeWith extension method (borrowed from RxUI), we can add multiple IDisposables to a CompositeDisposable (named disposables in the code samples). When we're all done, we can end all subscriptions with one call to disposables.Dispose().
To be sure, there's nothing we can do with Rx, that we wouldn't be able to do with vanilla .NET. The resulting code is just a lot easier to reason about, once you've adapted to the functional way of thinking.
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var process = new Process())
using (var disposables = new CompositeDisposable())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
// Raise the Process.Exited event when the process terminates.
process.EnableRaisingEvents = true;
// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
// Subscribe to ErrorData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.ErrorDataReceived))
.Subscribe(
eventPattern => error.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );
// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);
// Start process(ing)
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();
logs = output + Environment.NewLine + error;
success = process.ExitCode == 0;
}
}
We already discussed the first part, where we map our events to observables, so we can jump straight to the meaty part. Here we assign our observable to the processExited variable, because we want to use it more than once.
First, when we activate it, by calling Subscribe. And later on when we want to 'await' its first value.
var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );
// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);
// Start process(ing)
...
// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();
One of the problems with OP is that it assumes process.WaitForExit(processTimeOutMiliseconds) will terminate the process when it times out. From MSDN:
Instructs the Process component to wait the specified number of milliseconds for the associated process to exit.
Instead, when it times out, it merely returns control to the current thread (i.e. it stops blocking). You need to manually force termination when the process times out. To know when time out has occurred, we can map the Process.Exited event to a processExited observable for processing. This way we can prepare the input for the Do operator.
The code is pretty self-explanatory. If exitedSuccessfully the process will have terminated gracefully. If not exitedSuccessfully, termination will need to be forced. Note that process.Kill() is executed asynchronously, ref remarks. However, calling process.WaitForExit() right after will open up the possibility for deadlocks again. So even in the case of forced termination, it's better to let all disposables be cleaned up when the using scope ends, as the output can be considered interrupted / corrupted anyway.
The try catch construct is reserved for the exceptional case (no pun intended) where you've aligned processTimeOutMilliseconds with the actual time needed by the process to complete. In other words, a race condition occurs between the Process.Exited event and the timer. The possibility of this happening is again magnified by the asynchronous nature of process.Kill(). I've encountered it once during testing.
For completeness, the DisposeWith extension method.
/// <summary>
/// Extension methods associated with the IDisposable interface.
/// </summary>
public static class DisposableExtensions
{
/// <summary>
/// Ensures the provided disposable is disposed with the specified <see cref="CompositeDisposable"/>.
/// </summary>
public static T DisposeWith<T>(this T item, CompositeDisposable compositeDisposable)
where T : IDisposable
{
if (compositeDisposable == null)
{
throw new ArgumentNullException(nameof(compositeDisposable));
}
compositeDisposable.Add(item);
return item;
}
}
The problem is that if you redirect StandardOutput and/or StandardError the internal buffer can become full.
To solve the issues aforementioned you can run the process in separate threads. I do not use WaitForExit, I utilize the process exited event which will return the ExitCode of the process asynchronously ensuring it has completed.
public async Task<int> RunProcessAsync(params string[] args)
{
try
{
var tcs = new TaskCompletionSource<int>();
var process = new Process
{
StartInfo = {
FileName = 'file path',
RedirectStandardOutput = true,
RedirectStandardError = true,
Arguments = "shell command",
UseShellExecute = false,
CreateNoWindow = true
},
EnableRaisingEvents = true
};
process.Exited += (sender, args) =>
{
tcs.SetResult(process.ExitCode);
process.Dispose();
};
process.Start();
// Use asynchronous read operations on at least one of the streams.
// Reading both streams synchronously would generate another deadlock.
process.BeginOutputReadLine();
string tmpErrorOut = await process.StandardError.ReadToEndAsync();
//process.WaitForExit();
return await tcs.Task;
}
catch (Exception ee) {
Console.WriteLine(ee.Message);
}
return -1;
}
The above code is battle tested calling FFMPEG.exe with command line arguments. I was converting mp4 files to mp3 files and doing over 1000 videos at a time without failing. Unfortunately I do not have direct power shell experience but hope this helps.
For the benefit of readers I'm going to divide this to 2 Sections
Section A: Problem & how to handle similar scenarios
Section B: Problem recreation & Solution
Section A: Problem
When this problem happens - process appears in task manager, then
after 2-3sec disappears (its fine), then it waits for timeout and then
exception is thrown System.InvalidOperationException: Process must
exit before requested information can be determined.
& See Scenario 4 below
In your code:
Process.WaitForExit(ProcessTimeOutMiliseconds); With this you're waiting for Process to Timeout or Exit, which ever takes place first.
OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)anderrorWaitHandle.WaitOne(ProcessTimeOutMiliseconds); With this you're waiting for OutputData & ErrorData stream read operation to signal its complete
Process.ExitCode == 0 Gets status of process when it exited
Different settings & their caveats:
Scenario 1 (Happy Path): Process completes before the timeout, and thus your stdoutput and stderror also finishes before it and all is well.
Scenario 2: Process, OutputWaitHandle & ErrorWaitHandle timesout however stdoutput & stderror is still being read and completes after time out WaitHandlers. This leads to another exception ObjectDisposedException()
Scenario 3: Process times-out first (19 sec) but stdout and stderror is in action, you wait for WaitHandler's to times out (19 sec), causing a added delay of + 19sec.
Scenario 4: Process times out and code attempts to prematurely query Process.ExitCode resulting in the error System.InvalidOperationException: Process must exit before requested information can be determined.
I have tested this scenario over a dozen times and works fine, following settings have been used while testing
Size of Output stream ranging from 5KB to 198KB by initiating build of about 2-15 projects
Premature timeouts & process exits within the timeout window
Updated Code
.
.
.
process.BeginOutputReadLine();
process.BeginErrorReadLine();
//First waiting for ReadOperations to Timeout and then check Process to Timeout
if (!outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds) && !errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
&& !process.WaitForExit(ProcessTimeOutMiliseconds) )
{
//To cancel the Read operation if the process is stil reading after the timeout this will prevent ObjectDisposeException
process.CancelOutputRead();
process.CancelErrorRead();
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Timed Out");
Logs = output + Environment.NewLine + error;
//To release allocated resource for the Process
process.Close();
return (false, logs);
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Completed On Time");
Logs = output + Environment.NewLine + error;
ExitCode = process.ExitCode.ToString();
// Close frees the memory allocated to the exited process
process.Close();
//ExitCode now accessible
return process.ExitCode == 0 ? (true, logs) : (false, logs);
}
}
finally{}
EDIT:
After hours of playing around with MSBuild I was finally able to reproduce the issue at my system
Section B: Problem Recreation & Solution
MSBuild has -m[:number] switch which
is used to specify the maximum number of concurrent processes to use
when building.
When this is enabled, MSBuild spawns a number of nodes that lives on
even after the Build is complete. Now,
Process.WaitForExit(milliseconds) would wait never exit and
eventually timeout
I was able to solve this in couple of ways
Spawn MSBuild process indirectly through CMD
$path1 = """C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\repos\Test\Test.sln"" -maxcpucount:3"
$cmdOutput = cmd.exe /c $path1 '2>&1'
$cmdOutput
Continue to use MSBuild but be sure to set the nodeReuse to False
$filepath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"
$arg1 = "C:\Users\John\source\repos\Test\Test.sln"
$arg2 = "-m:3"
$arg3 = "-nr:False"
Start-Process -FilePath $filepath -ArgumentList $arg1,$arg2,$arg3 -Wait -NoNewWindow
Even If parallel build is not enabled, you could still prevent your process from hanging at WaitForExit by launching the Build via CMD & therefore you do not create a direct dependency on Build process
$path1 = """C:\....\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\Test.sln"""
$cmdOutput = cmd.exe /c $path1 '2>&1'
$cmdOutput
The 2nd approach is preferred since you do not want too many MSBuild nodes to be lying around.
Not sure if this is your issue, but looking at MSDN there seems to be some weirdness with the overloaded WaitForExit when you are redirecting output asynchronously. MSDN article recommends calling the WaitForExit that takes no arguments after calling the overloaded method.
Docs page located here. Relevant text:
When standard output has been redirected to asynchronous event handlers, it is possible that output processing will not have completed when this method returns. To ensure that asynchronous event handling has been completed, call the WaitForExit() overload that takes no parameter after receiving a true from this overload. To help ensure that the Exited event is handled correctly in Windows Forms applications, set the SynchronizingObject property.
Code modification might look something like this:
if (process.WaitForExit(ProcessTimeOutMiliseconds))
{
process.WaitForExit();
}

How to set Maximum method execution time

I've a method that contains a process that must be stopped in a deadline( ex: 3 seconds) whether it has finished or not, and I don't want to wait if it has finished executing before reaching that dead line.
using Process.WaitForExit(3000) makes the program wait 3s even if the process has stopped before reaching the limit.
One more thing, I'm using process.StandardOutput.ReadToEnd(); to read the execution result, I don't care if it returns null or empty string or whatever if it doesn't finish.
And I guess that timers will cause the same problem.
Any Ideas?
Exited event of your process can be handled for detecting exit time.
WaitForExit returns a Boolean value that indicates your process has reached the timeout before exit or not.
Test this code:
Process proc = new Process();
ProcessStartInfo procInfo = new ProcessStartInfo()
{
FileName = "d:/test.exe",
UseShellExecute = false,
RedirectStandardOutput = true
};
proc.StartInfo = procInfo;
proc.EnableRaisingEvents = true;
proc.Exited += (o, args) =>
{
MessageBox.Show(proc.StandardOutput.ReadToEnd());
};
proc.Start();
if (proc.WaitForExit(3000))
{
MessageBox.Show("YES");
}
else
{
MessageBox.Show("NO");
}

How can I stop async Process by CancellationToken?

I found beneath code for execute some process without freezing UI. This code is executed when 'Start Work' button is pressed. And I think users would stop this work by 'Stop' button. So I found this article at MSDN.. https://msdn.microsoft.com/en-us/library/jj155759.aspx . But, It was hard that applying this CancellationToken at this code.. Anyone can help this problem?
I use public static async Task<int> RunProcessAsync(string fileName, string args) method only.
Code (From https://stackoverflow.com/a/31492250):
public static async Task<int> RunProcessAsync(string fileName, string args)
{
using (var process = new Process
{
StartInfo =
{
FileName = fileName, Arguments = args,
UseShellExecute = false, CreateNoWindow = true,
RedirectStandardOutput = true, RedirectStandardError = true
},
EnableRaisingEvents = true
})
{
return await RunProcessAsync(process).ConfigureAwait(false);
}
}
// This method is used only for internal function call.
private static Task<int> RunProcessAsync(Process process)
{
var tcs = new TaskCompletionSource<int>();
process.Exited += (s, ea) => tcs.SetResult(process.ExitCode);
process.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
process.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);
bool started = process.Start();
if (!started)
{
//you may allow for the process to be re-used (started = false)
//but I'm not sure about the guarantees of the Exited event in such a case
throw new InvalidOperationException("Could not start process: " + process);
}
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return tcs.Task;
}
Usage :
var cancelToken = new CancellationTokenSource();
int returnCode = async RunProcessAsync("python.exe", "foo.py", cancelToken.Token);
if (cancelToken.IsCancellationRequested) { /* something */ }
When the start button clicked, it starts some python script.
When script is running and user wants to stop it, user presses stop button.
Then program executes below code.
cancelToken.Cancel();
Thank you very much for reading this question.
The simple answer is that you can just call process.Kill() when the token is canceled:
cancellationToken.Register(() => process.Kill());
But there are two problems with this:
If you attempt to kill a process that doesn't exist yet or that has already terminated, you get an InvalidOperationException.
If you don't Dispose() the CancellationTokenRegistration returned from Register(), and the CancellationTokenSource is long-lived, you have a memory leak, since the registrations will stay in memory as long as the CancellationTokenSource.
Depending on your requirements, and your desire for clean code (even at the cost of complexity) it may be okay to ignore problem #2 and work around problem #1 by swallowing the exception in a catch.
It's quite simple now:
process.WaitForExitAsync(token);

c# ProcessStartInfo.Start - reading output but with a timeout

If you want to start another process and wait (with time out) to finish you can use the following (from MSDN).
//Set a time-out value.
int timeOut=5000;
//Get path to system folder.
string sysFolder=
Environment.GetFolderPath(Environment.SpecialFolder.System);
//Create a new process info structure.
ProcessStartInfo pInfo = new ProcessStartInfo();
//Set file name to open.
pInfo.FileName = sysFolder + #"\eula.txt";
//Start the process.
Process p = Process.Start(pInfo);
//Wait for window to finish loading.
p.WaitForInputIdle();
//Wait for the process to exit or time out.
p.WaitForExit(timeOut);
//Check to see if the process is still running.
if (p.HasExited == false)
//Process is still running.
//Test to see if the process is hung up.
if (p.Responding)
//Process was responding; close the main window.
p.CloseMainWindow();
else
//Process was not responding; force the process to close.
p.Kill();
MessageBox.Show("Code continuing...");
If you want to start another process and read its output then you can use the following pattern (from SO)
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "Write500Lines.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
How can you combine the two to read all input, not get stuck in deadlock and have a timeout if the running process goes awry?
This technique will hang if the output buffer is filled with more that 4KB of data. A more foolproof method is to register delegates to be notified when something is written to the output stream. I've already suggested this method before in another post:
ProcessStartInfo processInfo = new ProcessStartInfo("Write500Lines.exe");
processInfo.ErrorDialog = false;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
Process proc = Process.Start(processInfo);
// You can pass any delegate that matches the appropriate
// signature to ErrorDataReceived and OutputDataReceived
proc.ErrorDataReceived += (sender, errorLine) => { if (errorLine.Data != null) Trace.WriteLine(errorLine.Data); };
proc.OutputDataReceived += (sender, outputLine) => { if (outputLine.Data != null) Trace.WriteLine(outputLine.Data); };
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
You don't have to combine the two - the Process class has an event that fires when output is sent to the StandardOutput - OutputDataReceived.
If you subscribe to the event, you will be able to read output as it arrives and in your main program loop you can still timeout.
you can try modifying the first method to something like this
Process p = Process.Start(pInfo);
string output = string.Empty;
Thread t = new Thread(() => output = p.StandardOutput.ReadToEnd() );
t.Start();
//Wait for window to finish loading.
p.WaitForInputIdle();
//Wait for the process to exit or time out.
p.WaitForExit(timeOut);
void OpenWithStartInfo()
{
ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe", "Default2.aspx");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process p = Process.Start(startInfo);
p.WaitForInputIdle();
//p.WaitForExit(2);
p.Kill();
}
You could also use the APM, like this:
Define a delegate for the ReadToEnd call:
private delegate string ReadToEndDelegate();
Then use the delegate to call the method like this:
ReadToEndDelegate asyncCall = reader.ReadToEnd;
IAsyncResult asyncResult = asyncCall.BeginInvoke(null, null);
asyncResult.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(10));
asyncCall.EndInvoke(asyncResult);
EDIT: Error handling removed for clarity.
Just add everything from the first example below the WaitForExit() call to the second example.
None of the above answers work for me when dealing with interactive promts. (My command sometimes promts a question to the user and that should also be covered by timeout).
This is my solution.
A disadvantage is that i don't get any output if we run in a timeout.
ReadToEnd() blocks the execution so we have to run it in another thread and kill this thread if the process runs into the specified timeout.
public static Tuple<string, string> ExecuteCommand(string command)
{
// prepare start info
var procStartInfo = new ProcessStartInfo("cmd", "/c " + command)
{
ErrorDialog = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
WorkingDirectory = #"C:\",
CreateNoWindow = true
};
// start process
var proc = new Process {StartInfo = procStartInfo};
proc.Start();
var error = "";
var output = "";
// read stdout and stderr in new thread because it is blocking
Thread readerThread = new(() =>
{
try
{
error = proc.StandardError.ReadToEnd().Trim();
output = proc.StandardOutput.ReadToEnd().Trim();
}
catch (ThreadInterruptedException e)
{
Debug.WriteLine("Interrupted!!");
}
});
readerThread.Start();
// wait for max 6 seconds
if (proc.WaitForExit(6_000))
{
// if command runs to an enc => wait for readerThread to collect error/output stream
readerThread.Join();
}
else
{
// if process takes longer than 6 seconds => kill reader thread and set error to timeout
readerThread.Interrupt();
error = "Timeout!";
}
// return output and error
return new Tuple<string, string>(output, error);
}

Categories