System.Diagnostics.Process: 重定向标准输入、输出和错误
[原文作者]:Lucian Wischik
[原文链接]:System.Diagnostics.Process: redirect StandardInput, StandardOutput, StandardError
有时我们运行一个外部适用程序,向里面输入数据然后获取它的输出。这往往很容易发生这样的死锁:
' 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")
' 这里将发生死锁,如果P写到输出接口的量达到12k
p.StandardInput.Close()
Dim op = p.StandardOutput.ReadToEnd()
p.WaitForExit()
p.Close()
Console.WriteLine("OUTPUT:") : Console.WriteLine(op)
End Using
这段程序将发生死锁,因为“Cat”首先从标准输入接口中读取,然后写到标准输出接口,最后再读取它,这样一直循环下去直到没有任何东西可读取。但是如果它的标准输出接口已经填满了,并且没有对象去读取,这样就不能继续往里面写东西,从而出现阻塞。
这里的12k仅是一个随机数,我不会关注于它...
' 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
MSDN文档中说:你可以通过异步读取操作来避免这些依赖和潜在的死锁;或者你可以通过创建两个线程,让其中一个独立线程来读取输出流来避免死锁。因此我们将这样来做:
使用线程来重定向就不会死锁
' GOOD CODE: 这里不会发生死锁.
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
posted on 2009-02-20 14:19:00 by vbcti 评论(0) 阅读(3632)