我有一个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; }
}
提前感谢您解决此问题或提出不同(更好)方法的任何帮助。
答案 0 :(得分:2)
流程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.
}