异步捕获多个进程的输出

时间:2015-02-01 07:45:37

标签: c# asynchronous redirectstandardoutput

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

每隔一段时间,我的JSON最终会成为两个抛出解析错误的对象。我不确定如何从同一对象中的两个不同进程获取数据。就好像OutputDataReceived()事件从不同的进程获取数据而不是它报告的Id。我尝试实现一些没有任何运气的锁定(这对我来说有点新,因为我来自经典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; }
}

提前感谢您解决此问题或提出不同(更好)方法的任何帮助。

1 个答案:

答案 0 :(得分:2)

流程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.
    }