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

标签 c# asynchronous redirectstandardoutput

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

我的 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; }
}

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

最佳答案

ID过程 can 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.
    }

关于c# - 异步捕获多个进程的输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28260600/

相关文章:

c# - 当您在 Windows 中运行 git push 命令时,控制台的标准输出重定向会发生什么

c# - 为什么我不能在c#中继承一个类?

c# - 如何向枚举添加扩展方法

c# - java脚本到C#的回调,c#和java脚本之间的异步编程

python - 进程的异步生成 : design question - Celery or Twisted

java - 使用 "> filename"时不打印到文件的 System.out.println

c# - 关于使用枚举作为参数和 if/else 条件的问题

c# - 即使它在那里也找不到 View ,它不是在寻找正确的扩展名

asynchronous - 立即解决 Observable - 相当于 $q.when()

redirectstandardoutput - 为什么 RedirectStandardOutput 没有必要的 ANSI 代码?