【问题标题】:Start multiple process and redirect output async启动多个进程并异步重定向输出
【发布时间】:2018-07-18 07:11:57
【问题描述】:

我正在用一些对我来说很难的事情来挑战自己..

我想要一个运行“从属终端”的“主终端”。 从站使用“重定向输出”选项 (RedirectStandardOutput) 启动。 从站必须异步运行,主站应管理从站的“ReadLine”。 当 slave 运行时,master 必须能够执行其他操作。

我的实际代码:

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();
    }

如您所见,我尝试了启动和不启动新线程。 线程看起来很好,我在从终端完成操作之前到达了 Main void 的末尾,但是我在从站的“ReadLine”请求中被阻塞了。用户必须按 Enter 才能继续,我不希望这样..

你能帮帮我吗? 您建议采用不同的方法吗?

谢谢,问候。

编辑 1: 我想管理从终端等待“进入”的情况,因为它可能由第三方开发,可能会忽略我说不要使用Console.ReadLine() 的指导方针(好吧,不是我的问题,但我喜欢愚蠢的证明软件)。 主终端正在管理未定义数量的从终端,并记录从终端的所有输出,并运行一个看门狗,如果它崩溃,它会重新启动一个从终端。 Master 还接收启动/停止/重启 Slave 的命令和/或其他命令。

【问题讨论】:

  • 我想我终于明白了,所以StartTerminal 本质上运行子进程,它可以包含一个ReadLine(或另一个命令,可以阻止程序流) .我认为您需要的是异步线程或任务,其中每个都应该调用您的 StartTerminal( ... ) 方法。我会尽快更新我的答案
  • 你不需要一个线程来启动一个进程。 根据定义,不同的进程在不同的线程上运行。只需致电process.Start()
  • 你到底想做什么?这段代码中没有与多线程相关的内容,只是两个进程通过它们的输入/输出流进行通信。如果您想并行或异步处理数据,有许多方法比使用两个线程快 100000 倍、更好、更具可扩展性。例如,Parallel.ForEach() 将使用所有内核并行处理数据。您要解决的实际问题是什么?
  • 如果您希望管道中的两个或更多工作人员处理数据,请检查数据流类,例如 ActionBlock、TransformBlock。

标签: c# multithreading asynchronous process


【解决方案1】:

Process.BeginOutputReadLine(); 和Process.BeginErrorReadLine(); 都执行asynchronously,根据Process Class 文档,这意味着它们不会阻塞程序执行(或者换句话说:程序不会等待这些方法完成执行) .

您可能希望在OutputDataReceived 和ErrorDataReceived 事件处理程序中设置一些布尔值来指示数据何时完成读取,并在 main 方法中添加一个循环,在执行这两个方法之前不允许程序完成。

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 )
}

编辑(删除不相关的编辑): Asynchronous tasks,treads and Parallel execution 在这个问题的答案中有描述。我相信任何一个答案都会满足您的需求。

基本上,在链接问题的答案中使用任何代码示例,只需在线程/任务/并行调用中调用您的 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){
       ..
      }
   }
}

【讨论】:

  • StartTerminal([params]); 之后的代码直到用户在从终端请求时按下 Enter 才会执行。我需要避免在需要时管理“自动输入”。
  • 您能否更新您的问题,解释您想要实现的目标(逐步),因为 ReadLine 方法仅在按下 Enter 时执行(提交一行)。如果您不需要用户的任何输入,可以完全删除 ReadLine 吗?如果您确实需要用户的输入,我不明白您为什么需要等待用户。您是否只等待“Enter”,因为从终端不“知道”服务器何时完成工作?
  • 问题已编辑。我想忽略从终端中不应出现的 ReadLine()。
  • Fianlaly 测试了代码!我不得不对其进行一些更改,但没关系;)我仍然不知道如何管理从属终端的Console.ReadLine()。它仍然阻塞我的主终端.. 启动进程时使用process.WaitForExit();,线程(当 ReadLine 调用时)进入System.Threading.ThreadState.WaitSleepJoin,但如果我这样做thread.Interrupt(),则会出现异常。有什么想法吗?
  • 这里的关键是异步启动所有线程/任务。如果您正确启动线程(这是您应该研究并使用简单用例测试的部分)单个线程将被阻塞(通过阻塞命令),但主进程应该继续运行。我不记得具体是怎么回事,但我认为这与我在回答中写的类似。
猜你喜欢
  • 1970-01-01
  • 2011-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 2011-03-30
  • 2013-09-06
相关资源
最近更新 更多