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

20,746

Solution 1

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.

Solution 2

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());
}

Solution 3

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

Solution 4

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

Solution 5

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
}
Share:
20,746
struppi
Author by

struppi

Always happy, never blue.

Updated on September 13, 2021

Comments

  • struppi
    struppi over 2 years

    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?

  • struppi
    struppi over 15 years
    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.
  • scottm
    scottm over 15 years
    Have you tried using the defualtsettings for the StreamReader, ie StreamReader sr = new StreamReader(proc.StandardOutput);
  • user1703401
    user1703401 over 15 years
    Yeah, that's it. There's only a 2k output buffer, it will hang when you don't empty it. BeginOutputReadLine + BeginErrorReadLine solves it.
  • struppi
    struppi over 15 years
    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.
  • Dom M.
    Dom M. almost 12 years
    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.
  • Bob Horn
    Bob Horn about 11 years
    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?
  • Justin Tanner
    Justin Tanner almost 11 years
    Process.Start is synchronous, but once it executes a command it returns, leaving it to the user to check when the command has completed.
  • Aron Lorincz
    Aron Lorincz over 8 years
    Looks like that was it! I call process.StandardOutput.ReadToEndAsync(); and process.StandardError.ReadToEndAsync();, after starting cmd.exe, then it does not hang.
  • Xena
    Xena almost 8 years
    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.
  • Xena
    Xena almost 8 years
    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.
  • Jack
    Jack about 5 years
    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?
  • Jon Skeet
    Jon Skeet about 5 years
    @Jack: I'm afraid I don't know, and don't have a Windows 7 box to test on
  • Jack
    Jack about 5 years
    @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/PBMtqGQT
  • Jon Skeet
    Jon Skeet about 5 years
    Not without being able to reproduce it myself, no.
  • Jack
    Jack about 5 years
    @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 =)
  • Aviad P.
    Aviad P. over 2 years
    This is it - I used the following, instead of more threads: gist.github.com/paviad/f2d635fa269561a15c05d79b46c33895
  • Jon Skeet
    Jon Skeet over 2 years
    @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 :)
  • Aviad P.
    Aviad P. over 2 years
    Well, tbh, it might be prettier to include the process exit task in the wait all...
  • Moolie
    Moolie about 2 years
    Don't have WaitForExitAsync in .NET 4, But ReadToEndAsync is available and helped me a lot!