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

时间:2018-07-18 07:11:58

标签: c# multithreading asynchronous process

我正在挑战自己对我来说很困难的东西。

我想要一个运行“从属终端”的“主终端”。 从站通过“重定向输出”选项(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();
    }

如您所见,无论是否启动新线程,我都尝试过。 在线程看起来不错的情况下,我在从属终端完成操作之前到达Main void的末尾,但随后我受从属的“ ReadLine”请求而被阻止。用户必须按Enter才能继续,我不希望这样做。

能帮我吗? 您是否建议其他方法?

谢谢。

修改1: 我想管理从属终端正在等待“ Enter”的情况,因为它可能由第三方开发,可能会忽略我说不使用Console.ReadLine()的准则(好吧,这不是我的问题,但是我喜欢愚蠢的证明软件)。 主终端管理着未定义数量的从终端,并记录了从终端的所有输出,并运行了一个看门狗,如果它崩溃,它将重启从终端。 主服务器还接收启动/停止/重启从设备的命令和/或其他命令。

1 个答案:

答案 0 :(得分:2)

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

您可能想设置一些布尔值,以指示何时在OutputDataReceivedErrorDataReceived事件处理程序中完成数据读取,并在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”,在用户按Enter之前,您无法做任何(合理的)操作。

在可能的情况下(尤其是在对诸如控制器之类的硬件进行编程时)使用触发器代替“ 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){
       ..
      }
   }
}