System.Diagnostics.Process: redirect StandardInput, StandardOutput, StandardError


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 As New 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)


End Using


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 As New 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)


End Using


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 As New 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)


End Using


 


 


”’ <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>


”’ <remarks>This function solves the deadlock problem mentioned at http://msdn.microsoft.com/en-us/library/system.diagnostics.p.standardoutput.aspx</remarks>


<Runtime.CompilerServices.Extension()> Sub InputAndOutputToEnd(ByVal p As Diagnostics.Process, ByVal StandardInput As String, ByRef StandardOutput As String, ByRef StandardError As String)


    If p Is Nothing Then Throw New ArgumentException(“p must be non-null”)


    ‘ Assume p has started. Alas there’s no way to check.


    If p.StartInfo.UseShellExecute Then Throw New ArgumentException(“Set StartInfo.UseShellExecute to false”)


    If (p.StartInfo.RedirectStandardInput <> (StandardInput IsNot Nothing)) Then Throw New ArgumentException(“Provide a non-null Input only when StartInfo.RedirectStandardInput”)


    If (p.StartInfo.RedirectStandardOutput <> (StandardOutput IsNot Nothing)) Then Throw New ArgumentException(“Provide a non-null Output only when StartInfo.RedirectStandardOutput”)


    If (p.StartInfo.RedirectStandardError <> (StandardError IsNot Nothing)) Then Throw New ArgumentException(“Provide a non-null Error only when StartInfo.RedirectStandardError”)


   


    Dim outputData As New InputAndOutputToEndData


    Dim errorData As New InputAndOutputToEndData


   


    If p.StartInfo.RedirectStandardOutput Then


        outputData.Stream = p.StandardOutput


        outputData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)


        outputData.Thread.Start(outputData)


    End If


    If p.StartInfo.RedirectStandardError Then


        errorData.Stream = p.StandardError


        errorData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)


        errorData.Thread.Start(errorData)


    End If


   


    If p.StartInfo.RedirectStandardInput Then


        p.StandardInput.Write(StandardInput)


        p.StandardInput.Close()


    End If


   


    If p.StartInfo.RedirectStandardOutput Then outputData.Thread.Join() : StandardOutput = outputData.Output


    If p.StartInfo.RedirectStandardError Then errorData.Thread.Join() : StandardError = errorData.Output


    If outputData.Exception IsNot Nothing Then Throw outputData.Exception


    If errorData.Exception IsNot Nothing Then Throw errorData.Exception


End Sub


 


Private Class InputAndOutputToEndData


    Public Thread As Threading.Thread


    Public Stream As IO.StreamReader


    Public Output As String


    Public Exception As Exception


End Class


 


Private Sub InputAndOutputToEndProc(ByVal data_ As Object)


    Dim data = DirectCast(data_, InputAndOutputToEndData)


    Try : data.Output = data.Stream.ReadToEnd : Catch e As Exception : data.Exception = e : End Try


End Sub



 


Comments (1)

  1. Andy Dennison says:

    Why wouldn't use  process.OutputDataReceived and process.ErrorDataReceived?

    That way you can echo (or capture) the output from both in the order received.

    This seems much simpler than async or threading to me.