In my web application I use wkhtmltopdf to process reports to output them as a PDF. I have a few functions that compile some HTML together, some headers etc. and then pass this information to wkhtmltopdf to compile the PDF and serve it to the user.
Something like:
public JsonResult BuildPDF(string one, string two, SomeData[] data, SomeList[] {
lock(PDFLock) {
// ... Code here to compile HTML and save to files
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"C:\inetpub\wwwroot\MySite\wkhtmltopdf.exe";
psi.UseShellExecute = true;
psi.Verb = "runas";
...
Process p = Process.Start(psi);
p.WaitForExit();
}
}
After the PDF is compiled I push it to the user then delete the file.
As you can see I have a lock around this function to prevent two attempts at processing a PDF at one time. On IIS Express this function behaves as I would expect: if two requests are made at the exact same time, the request that makes it in first will be processed, and the 2nd request will sit and wait on the lock until the first request is complete.
In release IIS, it appears to be ignoring this lock, and does not wait for the first request to be finished. It actually ends up skipping through the function so quickly that the first request is still running while the 2nd request completes (unsuccessfully), so the user receives a message that the request failed.
I am unsure why it would ignore this lock, or why it would work in debug (IIS Express).
Is there any possibility this is due to IIS's configuration?
Edit:
The issue with lock was a problem of multiple worker processes in IIS. I am testing Mutex again now with multiple processes.
Edit:
Mutex usage: the Mutex is declared in the class as private static Mutex mut = new Mutex();
public JsonResult BuildPDF(string one, string two, SomeData[] data, SomeList[] {
mut.WaitOne();
// ... Code here to compile HTML and save to files
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"C:\inetpub\wwwroot\MySite\wkhtmltopdf.exe";
psi.UseShellExecute = true;
psi.Verb = "runas";
...
Process p = Process.Start(psi);
p.WaitForExit();
//... Return some JSON to user
}
Then inside the Download method:
public virtual void Download() {
// ... Response headers and stuff
Response.TransmitFile(#"C:\inetpub\wwwroot\MySite\temppdfs\pdfout.pdf");
Response.End();
System.IO.File.Delete(#"C:\inetpub\wwwroot\MySite\temppdfs\pdfout.pdf");
mut.ReleaseMutex();
}
My understanding was a bit off on lock and Mutex usage.
The reason lock was not working is my application pool in IIS had multiple worker processes allowed (4), so the lock could not work cross-process.
I was using the Mutex improperly by declaring it outside the functions, now the proper usage of Mutex in my case (release IIS with multiple processes in the application pool):
public JsonResult BuildPDF(string one, string two, SomeData[] data, SomeList[] {
Mutex mut = new Mutex(false, #"Global\PDFMutex");
mut.WaitOne();
// ... Code here to compile HTML and save to files
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"C:\inetpub\wwwroot\MySite\wkhtmltopdf.exe";
psi.UseShellExecute = true;
psi.Verb = "runas";
...
Process p = Process.Start(psi);
p.WaitForExit();
mut.ReleaseMutex();
//... Return some JSON to user
}
This usage of Mutex appears to be successful across multiple processes and multiple functions.
Related
I have a .NET Core 2.1 service which runs on an Ubuntu 18.04 VM and calls Tesseract OCR 4.00 via a Process instance. I would like to use an API wrapper, but I could only find one available and it is only in beta for the latest version of Tesseract -- the stable wrapper uses version 3 instead of 4. In the past, this service worked well enough, but I have been changing it so that document/image data is written and read from disk less frequently in an attempt to improve speed. The service used to call many more external processes (such as ImageMagick) which were unnecessary due to the presence of an API, so I have been replacing those with API calls.
Recently I've been testing this with a sample file taken from real data. It's a faxed document PDF that has 133 pages, but is only 5.8 MB in spite of that due to grayscale and resolution. The service takes a document, splits it into individual pages, then assigns multiple threads (one thread per page) to call Tesseract and process them using Parallel.For. The thread limits are configurable. I am aware that Tesseract has its own multithreading environment variable (OMP_THREAD_LIMIT). I found in prior testing that setting it to "1" is ideal for our set up at the moment, but in my recent testing for this issue I have tried leaving it unset (dynamic value) with no improvement.
The issue is that unpredictably, when Tesseract is called, the service will hang for about a minute and then crash, with the only error showing in journalctl being:
dotnet[32328]: Error while reaping child. errno = 10
dotnet[32328]: at System.Environment.FailFast(System.String, System.Exception)
dotnet[32328]: at System.Environment.FailFast(System.String)
dotnet[32328]: at System.Diagnostics.ProcessWaitState.TryReapChild()
dotnet[32328]: at System.Diagnostics.ProcessWaitState.CheckChildren(Boolean)
dotnet[32328]: at System.Diagnostics.Process.OnSigChild(Boolean)
I can't find anything at all online for this particular error. It would seem to me, based on related research I've done on the Process class, that this is occurring when the process is exiting and dotnet is trying to clean up the resources it was using. I'm really at a loss as to how to even approach this problem, although I have tried a number of "guesses" such as changing thread limit values. There is no cross-over between threads. Each thread has its own partition of pages (based on how Parallel.For partitions a collection) and it sets to work on those pages, one at a time.
Here is the process call, called from within multiple threads (8 is the limit we normally set):
private bool ProcessOcrPage(IMagickImage page, int pageNumber, object instanceId)
{
var inputPageImagePath = Path.Combine(_fileOps.GetThreadWorkingDirectory(instanceId), $"ocrIn_{pageNumber}.{page.Format.ToString().ToLower()}");
string outputPageFilePathWithoutExt = Path.Combine(_fileOps.GetThreadOutputDirectory(instanceId),
$"pg_{pageNumber.ToString().PadLeft(3, '0')}");
page.Write(inputPageImagePath);
var cmdArgs = $"-l eng \"{inputPageImagePath}\" \"{outputPageFilePathWithoutExt}\" pdf";
bool success;
_logger.LogStatement($"[Thread {instanceId}] Executing the following command:{Environment.NewLine}tesseract {cmdArgs}", LogLevel.Debug);
var psi = new ProcessStartInfo("tesseract", cmdArgs)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
// 0 is not the default value for this environment variable. It should remain unset if there
// is no config value, as it is determined dynamically by default within OpenMP.
if (_processorConfig.TesseractThreadLimit > 0)
psi.EnvironmentVariables.Add("OMP_THREAD_LIMIT", _processorConfig.TesseractThreadLimit.ToString());
using (var p = new Process() { StartInfo = psi })
{
string standardErr, standardOut;
int exitCode;
p.Start();
standardOut = p.StandardOutput.ReadToEnd();
standardErr = p.StandardError.ReadToEnd();
p.WaitForExit();
exitCode = p.ExitCode;
if (!string.IsNullOrEmpty(standardOut))
_logger.LogStatement($"Tesseract stdOut:\n{standardOut}", LogLevel.Debug, nameof(ProcessOcrPage));
if (!string.IsNullOrEmpty(standardErr))
_logger.LogStatement($"Tesseract stdErr:\n{standardErr}", LogLevel.Debug, nameof(ProcessOcrPage));
success = p.ExitCode == 0;
}
return success;
}
EDIT 4: After much testing and discussion with Clint in chat, here is what we learned. The error is raised from a Process event "OnSigChild," that much is obvious from the stack trace, but there is no way to hook into the same event that raises this error. The process never times out given a timeout of 10 seconds (Tesseract typically only takes a few seconds to process a given page). Curiously, if the process timeout is removed and I wait on the standard output and error streams to close, it will hang for a good 20-30 seconds, but the process does not appear in ps auxf during this hang time. From the best that I can tell, Linux is able to determine that the process is done executing, but .NET is not. Otherwise, the error seems to be raised at the very moment that the process is done executing.
The most baffling thing to me is still that the process handling part of the code really hasn't changed very much compared to the working version of this code we have in production. This suggests that it's an error I made somewhere, but I am simply unable to find it. I think I will have to open up an issue on the dotnet GitHub tracker.
"Error while reaping child"
Processes hold up some resources in the kernel, On Unix, when the parent dies, it is the init process that is responsible for cleaning up the kernel resources both Zombine and Orphan process (aka reaping the child). .NET Core reaps child processes as soon as they terminate.
"I have discovered that removing the stdout and stderr stream ReadToEnd
calls causes the processes to end immediately instead of hang, with
the same error"
The error is due to the fact that you are prematurely calling p.ExitCode even before the process has finished and with the ReadToEnd you are just delaying this activity
Summary of updated code
StartInfo.FileName should point to a filename that you want to start
UseShellExecute to false if the process should be created directly from the executable file and true if you intend that shell should be used when starting the process;
Added asynchrnous read operations to standard ouput and error streams
AutoResetEvents to signal when the output and error when the operations complete
Process.Close() to release the resources
It is easier to set and use ArgumentList over Arguments property
Redhat Blog on NetProcess on Linux
Revised Module
private bool ProcessOcrPage(IMagickImage page, int pageNumber, object instanceId)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
int exitCode;
var inputPageImagePath = Path.Combine(_fileOps.GetThreadWorkingDirectory(instanceId), $"ocrIn_{pageNumber}.{page.Format.ToString().ToLower()}");
string outputPageFilePathWithoutExt = Path.Combine(_fileOps.GetThreadOutputDirectory(instanceId),
$"pg_{pageNumber.ToString().PadLeft(3, '0')}");
page.Write(inputPageImagePath);
var cmdArgs = $"-l eng \"{inputPageImagePath}\" \"{outputPageFilePathWithoutExt}\" pdf";
bool success;
_logger.LogStatement($"[Thread {instanceId}] Executing the following command:{Environment.NewLine}tesseract {cmdArgs}", LogLevel.Debug);
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 = "tesseract.exe", // Verify if this is indeed the process that you want to start ?
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
Arguments = cmdArgs,
WorkingDirectory = Path.GetDirectoryName(path)
};
if (_processorConfig.TesseractThreadLimit > 0)
process.StartInfo.EnvironmentVariables.Add("OMP_THREAD_LIMIT", _processorConfig.TesseractThreadLimit.ToString());
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 (!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");
//To release allocated resource for the Process
process.Close();
//Timed out
return false;
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Completed On Time");
exitCode = process.ExitCode;
if (!string.IsNullOrEmpty(standardOut))
_logger.LogStatement($"Tesseract stdOut:\n{standardOut}", LogLevel.Debug, nameof(ProcessOcrPage));
if (!string.IsNullOrEmpty(standardErr))
_logger.LogStatement($"Tesseract stdErr:\n{standardErr}", LogLevel.Debug, nameof(ProcessOcrPage));
process.Close();
return exitCode == 0 ? true : false;
}
}
Catch
{
//Handle Exception
}
}
}
So, I use my own .NET Process to start up a command line process like so:
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectError;
//startInfo.WindowStyle = ProcessWindowStyle.Hidden; //is this necessary for cmd line commands?
startInfo.RedirectStandardInput = false;
startInfo.UseShellExecute = false;
String arguments = "/C PYTHON_SCRIPT";
startInfo.Arguments = arguments;
String pathToProcess = "cmd.exe";
startInfo.FileName = pathToProcess;
startInfo.WorkingDirectory = workingDirectory;
p.StartInfo = startInfo;
p.Start();
The process executes just fine, and I get its output/errors. The problem comes when I want to kill it off before it is finished executing. Since cmd line technically started off the "PYTHON_SCRIPT" process itself, I don't know what the Process ID of that process is! Since that's the one I really want to kill off (not cmd line), I'm screwed. I have in fact killed off the cmd line process to see if it has any effect, and it doesn't.
Any help would be appreciated.
In case it wasn't clear, the question is: "How do I kill off the PYTHON_SCRIPT (or any other) process?"
EDIT: I'm sorry I wasn't clear... I'm using PYTHON_SCRIPT as a filler. I am starting many different processes this way, not all of which are python scripts. Some are batch files, some are python scripts, some are perl scripts, etc. I would much prefer a generalized solution here.
EDIT 2: The situation is a little more complex than it might appear from the question. For instance, I am using scons to build some code. Using "cmd /C scons" is easy. However, starting the scons process is much more difficult, because it is a python script. I pass in a different working directory, so "python.exe scons.py scons" won't work at all, and neither will "scons.bat scons" since scons.bat needs to find scons.py, and scons.py isn't in my working directory.
Finally found the answer through much perseverance and google foo! Here is a link to the stackoverflow answer:
https://stackoverflow.com/a/15281070/476298
And here is the website that pointed me in that direction:
http://stanislavs.org/stopping-command-line-applications-programatically-with-ctrl-c-events-from-net/
Looks like he did a whole bunch of research on this. Definitely want to give him all the credit. Hope this page helps others who haven't found it yet!
I had a little trouble figuring out some of the D11Import stuff for the WinAPI methods, so here's the code I used in case anyone else gets hung up:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(CtrlTypes CtrlType);
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);
///
/// Immediately halts all running processes under this ProcessManager's control.
///
public static void HaltAllProcesses()
{
for (int i = 0; i < runningProcesses.Count; i++)
{
Process p = runningProcesses[i];
uint pid = (uint)p.Id;
//This does not require the console window to be visible.
if (AttachConsole(pid))
{
//Disable Ctrl-C handling for our program
SetConsoleCtrlHandler(null, true);
GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
//Must wait here. If we don't and re-enable Ctrl-C
//handling below too fast, we might terminate ourselves.
p.WaitForExit(2000);
FreeConsole();
//Re-enable Ctrl-C handling or any subsequently started
//programs will inherit the disabled state.
SetConsoleCtrlHandler(null, false);
}
if (!p.HasExited)
{ //for console-driven processes, this won't even do anything... (it'll kill the cmd line, but not the actual process)
try
{
p.Kill();
}
catch (InvalidOperationException e)
{
controller.PrintImportantError("Process " + p.Id + " failed to exit! Error: " + e.ToString());
}
}
}
}
P.S. If it's not obvious from the code, I'm storing a list of multiple processes that are running simultaneously, and just looping through them one by one to kill them all.
if you can run a CMD process, and you know your processes PIDs, why don't you simply use TASKKILL /PID 1234 ?
What I didn't get here is where you're getting the PID from.
This link is very useful
// Get the current process.
Process currentProcess = Process.GetCurrentProcess();
// Get all instances of Notepad running on the local
// computer.
Process [] localByName = Process.GetProcessesByName("notepad");
// Get all instances of Notepad running on the specifiec
// computer.
// 1. Using the computer alias (do not precede with "\\").
Process [] remoteByName = Process.GetProcessesByName("notepad", "myComputer");
// 2. Using an IP address to specify the machineName parameter.
Process [] ipByName = Process.GetProcessesByName("notepad", "169.0.0.0");
// Get all processes running on the local computer.
Process [] localAll = Process.GetProcesses();
// Get all processes running on the remote computer.
Process [] remoteAll = Process.GetProcesses("myComputer");
// Get a process on the local computer, using the process id.
Process localById = Process.GetProcessById(1234);
// Get a process on a remote computer, using the process id.
Process remoteById = Process.GetProcessById(2345,"myComputer");
Use GetProcessesByName() and look for the "python.exe" or "pythonw.exe" process.
The .Exited is not working for all cases, for example: to C:\foo.png when I close the responsible application that show the image, I don't get the MessageBox.Show("exited!");
here's my code:
public static void TryOpenFile(string filename)
{
Process proc = new Process();
proc.StartInfo = new ProcessStartInfo(filename);
proc.EnableRaisingEvents = true;
proc.Exited += (a,b) => { MessageBox.Show("Exited!"); }
proc.Start();
}
how I call the function TryOpenFile(#"C:\foo.png");. How to fix this?
Is it possible that you already have your image editing program open? When you call proc.Start(), if the process is already running, then the existing process is reused. You should check the return value of proc.Start() to see if this is the case.
From MSDN:
Return Value
true if a process resource is started; false if no new
process resource is started (for example, if an existing process is
reused).
...
Remarks
...
If the process resource specified by the FileName member of the StartInfo property is
already running on the computer, no additional process resource is started. Instead, the
running process resource is reused and false is returned.
As is asked in this post, there are two ways to call another process in C#.
Process.Start("hello");
And
Process p = new Process();
p.StartInfo.FileName = "hello.exe";
p.Start();
p.WaitForExit();
Q1 : What are the pros/cons of each approach?
Q2 : How to check if error happens with the Process.Start() method?
With the first method you might not be able to use WaitForExit, as the method returns null if the process is already running.
How you check if a new process was started differs between the methods. The first one returns a Process object or null:
Process p = Process.Start("hello");
if (p != null) {
// A new process was started
// Here it's possible to wait for it to end:
p.WaitForExit();
} else {
// The process was already running
}
The second one returns a bool:
Process p = new Process();
p.StartInfo.FileName = "hello.exe";
bool s = p.Start();
if (s) {
// A new process was started
} else {
// The process was already running
}
p.WaitForExit();
For simple cases, the advantage is mainly convenience. Obviously you have more options (working path, choosing between shell-exec, etc) with the ProcessStartInfo route, but there is also a Process.Start(ProcessStartInfo) static method.
Re checking for errors; Process.Start returns the Process object, so you can wait for exit and check the error code if you need. If you want to capture stderr, you probably want either of the ProcessStartInfo approaches.
Very little difference. The static method returns a process object, so you can still use the "p.WaitForExit()" etc - using the method where you create a new process it would be easier to modify the process parameters (processor affinity and such) before launching the process.
Other than that - no difference. A new process object is created both ways.
In your second example - that is identical to this:
Process p = Process.Start("hello.exe");
p.WaitForExit();
I wrote a quick and dirty wrapper around svn.exe to retrieve some content and do something with it, but for certain inputs it occasionally and reproducibly hangs and won't finish. For example, one call is to svn list:
svn list "http://myserver:84/svn/Documents/Instruments/" --xml --no-auth-cache --username myuser --password mypassword
This command line runs fine when I just do it from a command shell, but it hangs in my app. My c# code to run this is:
string cmd = "svn.exe";
string arguments = "list \"http://myserver:84/svn/Documents/Instruments/\" --xml --no-auth-cache --username myuser --password mypassword";
int ms = 5000;
ProcessStartInfo psi = new ProcessStartInfo(cmd);
psi.Arguments = arguments;
psi.RedirectStandardOutput = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
StreamReader output = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8);
proc.WaitForExit(ms);
if (proc.HasExited)
{
return output.ReadToEnd();
}
This takes the full 5000 ms and never finishes. Extending the time doesn't help. In a separate command prompt, it runs instantly, so I'm pretty sure it's unrelated to an insufficient waiting time. For other inputs, however, this seems to work fine.
I also tried running a separate cmd.exe here (where exe is svn.exe and args is the original arg string), but the hang still occurred:
string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";
What could I be screwing up here, and how can I debug this external process stuff?
EDIT:
I'm just now getting around to addressing this. Mucho thanks to Jon Skeet for his suggestion, which indeed works great. I have another question about my method of handling this, though, since I'm a multi-threaded novice. I'd like suggestions on improving any glaring deficiencies or anything otherwise dumb. I ended up creating a small class that contains the stdout stream, a StringBuilder to hold the output, and a flag to tell when it's finished. Then I used ThreadPool.QueueUserWorkItem and passed in an instance of my class:
ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);
proc.WaitForExit(ms);
if (proc.HasExited)
{
bufferHandler.Stop();
return bufferHandler.ReadToEnd();
}
... and ...
private class ProcessBufferHandler
{
public Stream stream;
public StringBuilder sb;
public Encoding encoding;
public State state;
public enum State
{
Running,
Stopped
}
public ProcessBufferHandler(Stream stream, Encoding encoding)
{
this.stream = stream;
this.sb = new StringBuilder();
this.encoding = encoding;
state = State.Running;
}
public void ProcessBuffer()
{
sb.Append(new StreamReader(stream, encoding).ReadToEnd());
}
public string ReadToEnd()
{
return sb.ToString();
}
public void Stop()
{
state = State.Stopped;
}
}
This seems to work, but I'm doubtful that this is the best way. Is this reasonable? And what can I do to improve it?
One standard issue: the process could be waiting for you to read its output. Create a separate thread to read from its standard output while you're waiting for it to exit. It's a bit of a pain, but that may well be the problem.
Jon Skeet is right on the money!
If you don't mind polling after you launch your svn command try this:
Process command = new Process();
command.EnableRaisingEvents = false;
command.StartInfo.FileName = "svn.exe";
command.StartInfo.Arguments = "your svn arguments here";
command.StartInfo.UseShellExecute = false;
command.StartInfo.RedirectStandardOutput = true;
command.Start();
while (!command.StandardOutput.EndOfStream)
{
Console.WriteLine(command.StandardOutput.ReadLine());
}
I had to drop an exe on a client's machine and use Process.Start to launch it.
The calling application would hang - the issue ended up being their machine assuming the exe was dangerous and preventing other applications from starting it.
Right click the exe and go to properties. Hit "Unblock" toward the bottom next to the security warning.
Based on Jon Skeet's answer this is how I do it in modern day (2021) .NET 5
var process = Process.Start(processStartInfo);
var stdErr = process.StandardError;
var stdOut = process.StandardOutput;
var resultAwaiter = stdOut.ReadToEndAsync();
var errResultAwaiter = stdErr.ReadToEndAsync();
await process.WaitForExitAsync();
await Task.WhenAll(resultAwaiter, errResultAwaiter);
var result = resultAwaiter.Result;
var errResult = errResultAwaiter.Result;
Note that you can't await the standard output before the error, because the wait will hang in case the standard error buffer gets full first (same for trying it the other way around).
The only way is to start reading them asynchronously, wait for the process to exit, and then complete the await by using Task.WaitAll
I know this is an old post but maybe this will assist someone. I used this to execute some AWS (Amazon Web Services) CLI commands using .Net TPL tasks.
I did something like this in my command execution which is executed within a .Net TPL Task which is created within my WinForm background worker bgwRun_DoWork method which holding a loop with while(!bgwRun.CancellationPending). This contains the reading of the Standard Output from the Process via a new Thread using the .Net ThreadPool class.
private void bgwRun_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgwRun.CancellationPending)
{
//build TPL Tasks
var tasks = new List<Task>();
//work to add tasks here
tasks.Add(new Task(()=>{
//build .Net ProcessInfo, Process and start Process here
ThreadPool.QueueUserWorkItem(state =>
{
while (!process.StandardOutput.EndOfStream)
{
var output = process.StandardOutput.ReadLine();
if (!string.IsNullOrEmpty(output))
{
bgwRun_ProgressChanged(this, new ProgressChangedEventArgs(0, new ExecutionInfo
{
Type = "ExecutionInfo",
Text = output,
Configuration = s3SyncConfiguration
}));
}
if (cancellationToken.GetValueOrDefault().IsCancellationRequested)
{
break;
}
}
});
});//work Task
//loop through and start tasks here and handle completed tasks
} //end while
}
I know my SVN repos can run slow sometimes, so maybe 5 seconds isn't long enough? Have you copied the string you are passing to the process from a break point so you are positive it's not prompting you for anything?