我从C#启动一个进程如下:
public bool Execute() { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.Arguments = "the command"; startInfo.FileName = "C:\\MyApp.exe"; startInfo.UseShellExecute = false; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; Log.LogMessage("{0} {1}",startInfo.FileName,startInfo.Arguments); using (Process myProcess = Process.Start(startInfo)) { StringBuilder output = new StringBuilder(); myProcess.OutputDataReceived += delegate(object sender,DataReceivedEventArgs e) { Log.LogMessage(Thread.CurrentThread.ManagedThreadId.ToString() + e.Data); }; myProcess.ErrorDataReceived += delegate(object sender,DataReceivedEventArgs e) { Log.LogError(Thread.CurrentThread.ManagedThreadId.ToString() + " " + e.Data); }; myProcess.BeginErrorReadLine(); myProcess.BeginOutputReadLine(); myProcess.WaitForExit(); } return false; }
但是这有一个问题…如果有问题的应用程序按照这个顺序写入std out和std err:
std out: msg 1 std err: msg 2 std out: msg 3
那么我从日志中看到的输出是:
msg 2 msg 1 msg 3
这似乎是因为事件处理程序在另一个线程中执行.所以我的问题是如何保持流程写入std err和std out的顺序?
我想到使用时间戳,但我不认为这将工作,由于线程的抢先的性质.
更新:确认在数据上使用时间戳是没有用的.
最终更新:接受的答案解决了这个问题 – 但是它有一个缺点,当流合并时,无法知道哪个流被写入.因此,如果您需要写入stderr ==失败的逻辑,而不是应用程序退出代码,那么您仍然可以拧紧.
解决方法
据了解,您希望保留stdout / stderr消息的顺序.我没有看到任何DECENT的方法来做到这一点与C#管理的过程(反思 – 是的,讨厌的子类黑客 – 是的).看来它几乎是硬编码.
此功能不依赖于线程本身.如果要保持顺序,STDOUT和STDERROR必须使用相同的句柄(缓冲区).如果他们使用相同的缓冲区,它将被同步.
这是Process.cs的一个片段:
if (startInfo.RedirectStandardOutput) { CreatePipe(out standardOutputReadPipeHandle,out startupInfo.hStdOutput,false); } else { startupInfo.hStdOutput = new SafeFileHandle( NativeMethods.GetStdHandle( NativeMethods.STD_OUTPUT_HANDLE),false); } if (startInfo.RedirectStandardError) { CreatePipe(out standardErrorReadPipeHandle,out startupInfo.hStdError,false); } else { startupInfo.hStdError = new SafeFileHandle( NativeMethods.GetStdHandle( NativeMethods.STD_ERROR_HANDLE),false); }
如你所见,会有两个缓冲区,如果我们有两个缓冲区,我们已经丢失了订单信息.
基本上,您需要创建可以处理这种情况的您自己的Process()类.伤心?是.
好消息是,这不难,看起来很简单.这是从StackOverflow获取的代码,不是C#,但足以理解算法:
function StartProcessWithRedirectedOutput(const ACommandLine: string; const AOutputFile: string; AShowWindow: boolean = True; AWaitForFinish: boolean = False): Integer; var CommandLine: string; StartupInfo: TStartupInfo; ProcessInformation: TProcessInformation; StdOutFileHandle: THandle; begin Result := 0; StdOutFileHandle := CreateFile(PChar(AOutputFile),GENERIC_WRITE,FILE_SHARE_READ,nil,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0); Win32Check(StdOutFileHandle <> INVALID_HANDLE_VALUE); try Win32Check(SetHandleInformation(StdOutFileHandle,HANDLE_FLAG_INHERIT,1)); FillChar(StartupInfo,SizeOf(TStartupInfo),0); FillChar(ProcessInformation,SizeOf(TProcessInformation),0); StartupInfo.cb := SizeOf(TStartupInfo); StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESTDHANDLES; StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE); StartupInfo.hStdOutput := StdOutFileHandle; StartupInfo.hStdError := StdOutFileHandle; if not(AShowWindow) then begin StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESHOWWINDOW; StartupInfo.wShowWindow := SW_HIDE; end; CommandLine := ACommandLine; UniqueString(CommandLine); Win32Check(CreateProcess(nil,PChar(CommandLine),True,CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,StartupInfo,ProcessInformation)); try Result := ProcessInformation.dwProcessId; if AWaitForFinish then WaitForSingleObject(ProcessInformation.hProcess,INFINITE); finally CloseHandle(ProcessInformation.hProcess); CloseHandle(ProcessInformation.hThread); end; finally CloseHandle(StdOutFileHandle); end; end;
资料来源:How to redirect large amount of output from command executed by CreateProcess?
而不是文件,你想使用CreatePipe.从管道,您可以像这样读取异步:
standardOutput = new StreamReader(new FileStream( standardOutputReadPipeHandle,FileAccess.Read,4096,false),enc,true,4096);
和BeginReadOutput()
if (output == null) { Stream s = standardOutput.BaseStream; output = new AsyncStreamReader(this,s,new UserCallBack(this.OutputReadNotifyUser),standardOutput.CurrentEncoding); } output.BeginReadLine();