启动多个进程并异步重定向输出

3

我正在尝试挑战自己,学习一些对我来说很困难的东西。

我想要一个“主终端”,它可以运行“从终端”。 从终端是使用“重定向输出”选项(RedirectStandardOutput)启动的。 从终端必须异步运行,而主终端应该管理来自从终端的“ReadLine”。 在从终端运行时,主终端必须能够执行其他操作。

我的实际代码:

static void StartTerminal(string ExecPath, int DefaultPort, string Arguments = "")
    {
        int port = DefaultPort;
        if (usedPorts.Contains(port)) port = nextPort;
        while(usedPorts.Contains(nextPort))
        {
            nextPort++;
            port = nextPort;
        }
        usedPorts.Add(port);
        string _arguments = "/port:" + port;
        _arguments += Arguments;

        //* Create your Process
        Process process = new Process();
        process.StartInfo.FileName = "dotnet ";
        process.StartInfo.Arguments = ExecPath + " /c " + _arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        //* Set your output and error (asynchronous) handlers
        process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
        process.ErrorDataReceived += new DataReceivedEventHandler(OutErrorHandler);
        //* Start process and handlers

        ThreadStart ths = new ThreadStart(() => {
            bool ret = process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
        });
        Thread th = new Thread(ths);
        th.Start();

        //process.Start();
        //process.BeginOutputReadLine();
        //process.BeginErrorReadLine();
        //process.WaitForExit();
    }

如您所见,我尝试了使用和不使用新线程。使用线程看起来没问题,我在主void结束之前完成了操作,但是我被从属终端的“ReadLine”请求阻塞了。用户必须按Enter键才能继续,我不想这样做。
请问您能帮助我吗?您建议采用不同的方法吗?
谢谢,问候。
编辑1: 我想处理从属终端等待“Enter”的情况,因为它可能由第三方开发,他们可能会忽略我的指导,其中我说不要使用Console.ReadLine() (好的,这不是我的问题,但我喜欢傻瓜证明软件)。 主终端正在管理未定义数量的从属终端,并记录所有从属终端的输出以及运行监视器以重新启动崩溃的从属终端。 主机还接收命令以启动/停止/重新启动从属终端和/或其他命令。

我想我终于明白了,所以 StartTerminal 本质上运行子进程,这些子进程可能包含一个 ReadLine(或另一个可以阻塞程序流的命令)。我认为你需要异步线程或任务,每个线程或任务都应该调用你的 StartTerminal( ... ) 方法。我会很快更新我的答案。 - Zero
你不需要一个“线程”来启动一个进程。不同的进程通过定义在不同的线程上运行。只需调用 process.Start() 即可。 - Panagiotis Kanavos
你到底想做什么?这段代码与多线程无关,只是两个进程通过输入/输出流进行通信。如果你想并行或异步处理数据,有很多方法比使用两个线程快100000倍、更好、更可扩展。例如,Parallel.ForEach()将使用所有核心并行处理数据。你真正想解决的问题是什么? - Panagiotis Kanavos
如果您想让管道中的两个或多个工作器处理数据,请检查Dataflow类,例如ActionBlock、TransformBlock。 - Panagiotis Kanavos
1个回答

2

Process.BeginOutputReadLine();Process.BeginErrorReadLine();根据Process Class文档的说明都是异步执行的,这意味着它们不会阻塞程序执行(换句话说:程序不会等待这些方法执行完毕)。

您可能希望在OutputDataReceivedErrorDataReceived事件处理程序中设置一些布尔值来指示何时完成数据读取,并在主方法中添加一个循环,以确保在这两个方法执行之前程序不会结束。

private void DataReceivedEventHandler( [parameters] ){
    //I believe this is the part where you read the actual stream
    outputDataReceived = true;
}//do the same for ErrorDataReceived
while (!outputDataReceived && !errorDataReceived){
   wait(1000); //The actual method might be different, maybe sleep( 1000 ) or thread.sleep ( you can also set a different interval )
}
编辑(删除了不相关的编辑):异步任务、线程和并行执行在这个问题的答案中有描述。我相信其中任何一个答案都能满足您的需求。
基本上,使用链接问题答案中的任何代码示例,只需在线程/任务/并行调用中调用您的StartTerminal( )方法。使用答案中的代码创建异步处理,并实现逻辑以防止程序在所有线程完成工作之前达到其结尾。伪代码:
List<Thread> threads = new List<Thread>();

private void StartTerminal(int id, params){
     //all of your code keep it asynchornous

    while( !outputDataReceived && !errorDataReceived ){
        sleep( 1000 ); //delay between checks
    } //Makes sure this thread does not close until data is received, implement any other logic that should keep the thread alive here
}

public static void main(...){

    foreach(ParameterSet params in Parameters){ //Create thread list with different parameters
       var thread =  new Thread( StartTerminal(params) );
       threads.Add( thread );
    }

    while( !threads.isEmpty() ) ){ //if it is empty, all threads finished the job and got deleted, loop is skipped and program closes
        var threadsToRemove = new List<Thread>();
        foreach(Thread t in threads){ //start unstarted threads
          if(t.ThreadState == ThreadState.Unstarted){
            threads[i].Start( ).
          }elseif(t.ThreadState == ThreadState.Stopped){ //remove stopped threads
            threadsToRemove.Add(t).
          }//Implement other logic for suspended, aborted and other threads...
        }
        foreach(Thread t in threadsToRemove){
           threads.Remove(t);
        }

        sleep(1000); //delay between checks
    }
}

编辑2:将这些模式提供给您的从属终端开发人员(如果他们不知道如何自己完成,而不会阻止一切),因为一旦同步调用'ReadLine',在用户按下回车之前,您无法做任何事情(合理的)。
尽可能使用触发器而不是'ReadLines'来进行用户输入,特别是在编程硬件控制器等情况下。
HardwareButtonEnter.Press += delegate{ userInput = eventParameters.userInput; 
    UserInputReceived = true;}; //This is really hardware API specific code, only the boolean is important

//start an async thread/process for reading master terminal input.
while(1 = 1){
   if(UserInputReceived){
      ProcessUserInput().
   }elseif(masterTerminalDataReceived){
      ProcessMasterTerminalData().
   }
}

如果您无法使用触发器(例如在编写控制台应用程序时),请将阻塞语句放入异步线程中,并在线程完成时处理它们。
//Start your (async) ReadLine thread. Make sure it also sets UserInputReceived = true when it ends
//Start your async process read thread. also get it to set some boolean.
while( 1 = 1){
   if(threads.Any( t => t.Status = Finished ){
      if(UserInputReceived){
      }elseif(terminalInputReceived){
       ..
      }
   }
}

在用户按下从属终端请求时的Enter键之前,StartTerminal([params]);后面的代码不会执行。我需要避免这种情况,通过在必要时进行"自动Enter"来解决。 - Pietro di Caprio
你能否更新一下你的问题,解释一下你想要实现什么(逐步),因为ReadLine方法只有在按下Enter键时才会执行(提交一行)。如果你不需要用户输入任何内容,也许可以完全删除ReadLine?如果你确实需要用户输入,我不明白为什么你需要等待用户。你只是等待“Enter”键吗,因为从属终端不知道服务器何时完成工作? - Zero
问题已编辑。我想忽略在从属终端中不应存在的 ReadLine()。 - Pietro di Caprio
最终测试了代码!我不得不稍微改动一下,但没问题;) 我仍然不知道如何管理从属终端的 Console.ReadLine()。它仍然会阻塞我的主终端...在启动进程时使用 process.WaitForExit();,线程(当ReadLine调用时)进入 System.Threading.ThreadState.WaitSleepJoin,但是如果我执行 thread.Interrupt(),就会出现异常。有什么想法吗? - Pietro di Caprio
当使用process.WaitForExit();时,我看到了阻塞/非阻塞的区别。这样我就可以避免主线程被阻塞,但是我无法向终端发送命令以“跳过”Console.ReadLine()。 这不是真正的问题,我会尽快想办法解决。 非常感谢! - Pietro di Caprio
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接