Interprozesskommunikation für Arme

Im Rahmen der Live Demux Erweiterung von VCR.NET ergab sich folgendes Problem: ein Dienst startet ein Programm A und wartet, bis dieses beendet wird. Dieses Program wiederum startet ein weiteres Programm B. In seltenen Fällen kommt es dazu, dass A sich selbst vor B beendet (ist eine Schutzmassnahme, Details führen hier zu weit). Das hat aber leider den Effekt, dass der Dienst denkt, die Arbeit von A wäre erledigt – ist sie aber nicht.

Die auf die Schnelle implementierte Lösung ist recht einfach und nutzt eine Technik, die möglicherweise nicht weit verbreitet ist, aber deren Anwendung extrem einfach ist. Zudem hier ein erhebliches Potential steckt, dazu mehr am Ende.

Die Änderung ist, dass beim Starten von A auf einen speziellen Befehlszeilenparameter geprüft wird, der sicher nie verwendet wird – hier: /Slave. Ist dieser nicht vorhanden, so startet A sich selbst und fügt diesen Parameter an die Befehlszeile an. Ist dann /Slave gesetzt, so führt A die gewohnen Arbeiten durch, fast wie vorher.

Hier der Code zum Starten der zweiten A Instanz:

// Parameter helper
StringBuilder cmdLine = new StringBuilder();

// Append switch
cmdLine.Append("/Slave");

// Merge all
foreach (string arg in realArgs) cmdLine.AppendFormat(" \"{0}\"", arg.Replace("\"", "\"\""));

// Create start information
ProcessStartInfo info = new ProcessStartInfo();

// Configure
info.Arguments = cmdLine.ToString();
info.FileName = Application.ExecutablePath;
info.LoadUserProfile = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardInput = true;

// Create the process
Process process = Process.Start(info);

Interessant ist, dass eine Umleitung von Standardeingabe und Ausgabe angefordert wird. In der zweiten A Instanz beim eigentlichen Arbeiten wurde nun nach Starten von B nur folgender Code ergänzt – sämtliche Restfunktionalität bleibt unverändert:

// Create the process
m_Demux = Process.Start(info);

// Send to controller
Console.Out.WriteLine(m_Demux.Id);
Console.Out.Flush();

// Synchronize with caller
Console.In.ReadLine();

Die echte A Instanz sendet die Prozesskennung von B an die steuernde A Instanz und wartet dann auf eine Freigabe von dieser. Dieses Warten ist wichtig, damit B nicht schon fertig ist und die Prozesskennung schon wieder neu vergeben ist, bevor die Steuerinstanz die Rückgabe auswertet – sehr unwahrscheinlich, aber nicht unmöglich! Die Steuerinstanz liest nun die Kennung und wartet, bis die echte A Instanz und B beendet sind:

// Read the process identifier of the sub process
int demuxId = int.Parse(process.StandardOutput.ReadLine());

// Attach to the process
Process demux;

// Load
demux = Process.GetProcessById(demuxId);

// Let it continue
process.StandardInput.WriteLine("OK");
process.StandardInput.Flush();

// Wait for outer
process.WaitForExit();

// Wait for inner
if (null != demux) demux.WaitForExit();

Diese einfache Kommunikation eines Prozesses mit einem gestarteten Kindprozeß über die Standardkanäle hat eine sehr mächtige Erweiterungsmöglichkeit: es ist genau so einfach möglich, über den BinaryFormatter serialisierbare Objekte zwischen den Prozessen auszutauschen. Also erzeugt die Steuerinstanz etwa ein solches Objekt, initialisiert es und serialisiert es dann auf StandardInput des Kindprozesses. Dieser wartet mit einer Deserialisierung auf seinem Console.In, rekonstruiert das Objekt, führt entsprechende Aktionen aus und sendet via Console.Out und BinaryFormatter eine Antwort an die Steuerinstanz.

Genauso kommuniziert VCR.NET sehr erfolgreich mit den Aufzeichnungsprozessen.

Happy Coding

Jochen