c# - 异步捕获多个进程的输出

我有一个Windows窗体应用程序,可同时启动多达X个控制台进程。我从每个输出中获取JSON输出(一个对象),并将其解析为统计数据。我一直在跟踪我已经启动了多少个进程,并在OutputDataReceived()中捕获它们的输出。我使用进程ID作为键将输出附加到ConcurrentBag<object>中。

通常,我的JSON最终会变成两个对象,从而引发解析错误。我不确定如何最终获得来自同一对象中两个不同进程的数据。好像OutputDataReceived()事件正在从与其报告的ID不同的进程获取数据。我尝试实现一些锁定而没有任何运气(由于我来自Classic VB背景,这对我来说有点新)。

这是一些相关的代码:

private object _lockObj = new object();
private ConcurrentBag<ProcData> _procDatas;

// This is called up to X times
private void LaunchProc(int itemId)
{
    var proc = new Process();
    proc.StartInfo.CreateNoWindow = true;
    proc.StartInfo.ErrorDialog = false;
    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.RedirectStandardError = true;
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.RedirectStandardOutput = true;
    proc.EnableRaisingEvents = true;
    proc.Exited += proc_Exited;
    proc.OutputDataReceived += proc_OutputDataReceived;
    proc.ErrorDataReceived += proc_ErrorDataReceived;
    proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    proc.StartInfo.FileName = "someapp.exe";
    proc.StartInfo.Arguments = "/id=" + itemId;
    proc.Start();

    proc.BeginErrorReadLine();
    proc.BeginOutputReadLine();
}

// I assume I'm screwing something up here since this is the only place where I set OutputData
void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    var proc = sender as System.Diagnostics.Process;

    if (proc == null) return;
    if (e.Data == null) return;

    lock (_lockObj)
    {
        var item = _procDatas.FirstOrDefault(pi => pi.Id == proc.Id);
        if (item == null)
            _procDatas.Add(new ProcData() {Id = proc.Id, OutputData = e.Data});
        else
            item.OutputData += e.Data;
    }
}

void proc_Exited(object sender, EventArgs e)
{
    var proc = sender as System.Diagnostics.Process;
    ProcMessage procMsg = null;
    lock (_lockObj)
    {
        var procInfo = _procDatas.FirstOrDefault(pi => pi.Id == proc.Id);
        // JSON is parsed here and error is thrown because of multiple objects (eg {"ProcessId":1,"Msg":"Success"}{"ProcessId":2,"Msg":"Success"})
        procMsg = new ProcMessage(procInfo.OutputData);
    }
}

public class ProcData
{
    public int Id { get; set; }
    public string OutputData { get; set; }
    public string ErrorData { get; set; }
}


在此先感谢您对解决此问题或提出其他(更好)方法的帮助。

最佳答案

进程IDcan get reused‌​。因此,我建议不要在进程退出后使用进程ID作为标识符。

相反,您可以使用itemId作为标识符,将流程输出与之关联,并将流程和itemId封装在某个容器中(例如,经过轻微测试,看起来还可以):

public class ProcessExitedEventArgs<TKey> : EventArgs
{
    public ProcessExitedEventArgs(TKey key, string[] output)
    {
        this.Key = key;
        this.Output = output;
    }

    public TKey Key { get; private set; }
    public string[] Output { get; private set; }
}

public delegate void ProcessExitedEventHandler<TKey>(object sender, ProcessExitedEventArgs<TKey> e);

public class ProcessLauncher<TKey>
{
    public string FileName { get; private set; }
    public string Arguments { get; private set; }
    public TKey Key { get; private set; }

    object locker = new object();
    readonly List<string> output = new List<string>();
    Process process = null;
    bool launched = false;

    public ProcessLauncher(string fileName, string arguments, TKey key)
    {
        this.FileName = fileName;
        this.Arguments = arguments;
        this.Key = key;
    }

    public event ProcessExitedEventHandler<TKey> Exited;

    public bool Start()
    {
        lock (locker)
        {
            if (launched)
                throw new InvalidOperationException();
            launched = true;
            process = new Process();
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.ErrorDialog = false;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.RedirectStandardInput = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.EnableRaisingEvents = true;
            process.Exited += new EventHandler(proc_Exited);
            process.OutputDataReceived += proc_OutputDataReceived;
            process.ErrorDataReceived += proc_ErrorDataReceived;
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.StartInfo.FileName = FileName;
            process.StartInfo.Arguments = Arguments;
            try
            {
                var started = process.Start();

                process.BeginErrorReadLine();
                process.BeginOutputReadLine();
                return started;
            }
            catch (Exception)
            {
                process.Dispose();
                process = null;
                throw;
            }
        }
    }

    void proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (e.Data != null)
        {
            // Fill in as appropriate.
            Debug.WriteLine(string.Format("Error data received: {0}", e.Data));
        }
    }

    void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (e.Data == null)
            return;
        lock (locker)
        {
            output.Add(e.Data);
        }
    }

    void proc_Exited(object sender, EventArgs e)
    {
        lock (locker)
        {
            var exited = Exited;
            if (exited != null)
            {
                exited(this, new ProcessExitedEventArgs<TKey>(Key, output.ToArray()));
                // Prevent memory leaks by removing references to listeners.
                Exited -= exited;
            }
        }
        var process = Interlocked.Exchange(ref this.process, null);
        if (process != null)
        {
            process.OutputDataReceived -= proc_OutputDataReceived;
            process.ErrorDataReceived -= proc_ErrorDataReceived;
            process.Exited -= proc_Exited;
            process.Dispose();
        }
    }
}


(此类还确保处理已完成。)然后按以下方式使用它:

    public void LaunchProc(int itemId)
    {
        var launcher = new ProcessLauncher<int>("someapp.exe", "/id=" + itemId, itemId);
        launcher.Exited += launcher_Exited;
        launcher.Start();
    }

    void launcher_Exited(object sender, ProcessExitedEventArgs<int> e)
    {
        var itemId = e.Key;
        var output = e.Output;

        // Process output and associate it to itemId.
    }

本文翻译自 https://stackoverflow.com/questions/28260600/

网站遵循 CC BY-SA 4.0 协议,转载或引用请注明出处。

标签 c# asynchronous redirectstandardoutput


相关文章:

c# - 使实体框架将存储过程结果集列解释为bool而不是int?

c# - 在此LINQ语句中,“ i”的值从哪里获得?

c# - Protobuf-net错误:不需要类型,并且不能推断出任何合同:BlockHeader

javascript - 向DomOutline事件处理程序添加Promise

c# - 为什么异步LINQ Select lambda不需要返回值

c# - 使用异步控制器的强类型RedirectToAction(未来)

c# - 何时填充BeginRead的缓冲区?

c# - 控制台应用程序不会定期刷新输出

java - 将数据从标准输出写入Java中的文本文件不起作用