Sometimes you want to launch an external utility and send input to it and also capture its output. But it's easy to run into deadlock this way...

' BAD CODE

Using p AsNew System.Diagnostics.Process

p.StartInfo.FileName = "cat"

p.StartInfo.UseShellExecute = False

p.StartInfo.RedirectStandardOutput = True

p.StartInfo.RedirectStandardInput = True

p.Start()

p.StandardInput.Write("world" & vbCrLf & "hello")

' deadlock here if p needs to write more than 12k to StandardOutput

p.StandardInput.Close()

Dim op = p.StandardOutput.ReadToEnd()

p.WaitForExit()

p.Close()

Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

EndUsing

The deadlock in this case arises because "cat" (a standard unix utility) first reads from StandardInput, then writes to StandardOutput, then reads again, and so on until there's nothing left to read. But if its StandardOutput fills up with no one to read it, then it can't write any more, and blocks.

The number "12k" is arbitrary and I wouldn't rely on it...

' BAD CODE

Using p AsNew System.Diagnostics.Process

p.StartInfo.FileName = "findstr"

p.StartInfo.UseShellExecute = False

p.StartInfo.RedirectStandardOutput = True

p.StartInfo.RedirectStandardError = True

p.Start()

' deadlock here if p needs to write more than 12k to StandardError

Dim op = p.StandardOutput.ReadToEnd()

Dim err = p.StandardError.ReadToEnd()

p.WaitForExit()

Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

Console.WriteLine("ERROR:") : Console.WriteLine(err)

EndUsing

The MSDN documentation says, "You can use asynchronous read operations to avoid these dependencies and their deadlock potential. Alternately, you can avoid the deadlock condition by creating two threads and reading the output of each stream on a separate thread." So that's what we'll do...

Using threads to redirect without deadlock

' GOOD CODE: this will not deadlock.

Using p AsNew Diagnostics.Process

p.StartInfo.FileName = "sort"

p.StartInfo.UseShellExecute = False

p.StartInfo.RedirectStandardOutput = True

p.StartInfo.RedirectStandardInput = True

p.Start()

Dim op = ""

' do NOT WaitForExit yet since that would introduce deadlocks.

p.InputAndOutputToEnd("world" & vbCrLf & "hello", op, Nothing)

p.WaitForExit()

p.Close()

Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

EndUsing

''' <summary>

''' InputAndOutputToEnd: a handy way to use redirected input/output/error on a p.

''' </summary>

''' <param name="p">The p to redirect. Must have UseShellExecute set to false.</param>

''' <param name="StandardInput">This string will be sent as input to the p. (must be Nothing if not StartInfo.RedirectStandardInput)</param>

''' <param name="StandardOutput">The p's output will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardOutput)</param>

''' <param name="StandardError">The p's error will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardError)</param>