微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c# – 以正确的顺序捕获进程stdout和stderr

我从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();

原文地址:https://www.jb51.cc/csharp/95239.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐