Hanging process when run with .NET Process.Start -- what's wrong?
Asked Answered
F

6

34

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?

File answered 13/1, 2009 at 16:15 Comment(0)
K
42

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.

Kimberli answered 13/1, 2009 at 16:19 Comment(11)
Yeah, that's it. There's only a 2k output buffer, it will hang when you don't empty it. BeginOutputReadLine + BeginErrorReadLine solves it.Rhineland
How did you know it was only a 2k output buffer? In my application, I'm seeing this hang occur on one server, but not another... wondering if it could be due to the size of the output buffer.Astra
Looks like that was it! I call process.StandardOutput.ReadToEndAsync(); and process.StandardError.ReadToEndAsync();, after starting cmd.exe, then it does not hang.Eulalia
You can also choose not to read stdout at all (if you don't need it). command.StartInfo.RedirectStandardOutput = false does the trick for me.Maes
I know it's an old question but I'm having this issue on windows 7 only. I'm using Process.BeginOutputReadLine() can still be the process waiting for read the stdout?Pronouncement
@Jack: I'm afraid I don't know, and don't have a Windows 7 box to test onKimberli
@JonSkeet: Any other guess on what could be causing the process to hang only on windows 7? I'm hours on this, clueless on what could be. I start the process like this pastebin.com/PBMtqGQTPronouncement
Not without being able to reproduce it myself, no.Kimberli
@JonSkeet I find out. While the called process is supposed to not read anything from stdin, it seems was done so. I find out by setting RedirectStandardInput = true and closing the stdin stream proc.StandardInput.Close();. I left this comment for those that have had same issue as me =)Pronouncement
This is it - I used the following, instead of more threads: gist.github.com/paviad/f2d635fa269561a15c05d79b46c33895Heathen
@AviadP.: You might want to add that as a separate answer. Bear in mind this answer was first written in 2009, long before async/await :)Kimberli
D
19

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());
}
Demicanton answered 15/7, 2009 at 0:31 Comment(3)
I know this is four years later, but I'm struggling to understand how the above code can work. command.Start() is synchronous, right? If that's the case, how will the above code ever get to the next line?Jos
Process.Start is synchronous, but once it executes a command it returns, leaving it to the user to check when the command has completed.Demicanton
The above does not solve the problem of Process.Start hanging... but it does tell us when the process is done executing its command before moving on.Maes
H
5

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

Heathen answered 8/9, 2021 at 17:45 Comment(2)
Well, tbh, it might be prettier to include the process exit task in the wait all...Heathen
Don't have WaitForExitAsync in .NET 4, But ReadToEndAsync is available and helped me a lot!Sustain
D
4

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.

enter image description here

Despumate answered 13/8, 2018 at 18:42 Comment(0)
A
1

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
}
Alsoran answered 28/4, 2016 at 15:5 Comment(0)
E
0

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?

Entremets answered 13/1, 2009 at 16:21 Comment(3)
Yeah, I can run the exact command without prompts from the command line. This call returns in well under a second from teh command line, and I have tried waiting up to 30 seconds in my app with no love, so it sure seems to be AWOL.File
Have you tried using the defualtsettings for the StreamReader, ie StreamReader sr = new StreamReader(proc.StandardOutput);Entremets
Yeah, I can't do that in general because I need the specific UTF-8 encoding for the xml, but in specific cases where the default encoding works, there was no change.File

© 2022 - 2024 — McMap. All rights reserved.