Von diesem Forum an 'Josh'.
Application.Quit()
und Process.Kill()
sind mögliche Lösungen, haben sich aber als unzuverlässig erwiesen. Wenn Ihre Hauptanwendung stirbt, werden immer noch untergeordnete Prozesse ausgeführt. Was wir wirklich wollen, ist, dass die untergeordneten Prozesse sterben, sobald der Hauptprozess stirbt.
Die Lösung besteht darin, "Jobobjekte" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx zu verwenden .
Die Idee ist, ein "Jobobjekt" für Ihre Hauptanwendung zu erstellen und Ihre untergeordneten Prozesse beim Jobobjekt zu registrieren. Wenn der Hauptprozess abbricht, kümmert sich das Betriebssystem um das Beenden der untergeordneten Prozesse.
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public Int16 LimitFlags;
public UInt32 MinimumWorkingSetSize;
public UInt32 MaximumWorkingSetSize;
public Int16 ActiveProcessLimit;
public Int64 Affinity;
public Int16 PriorityClass;
public Int16 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UInt32 ProcessMemoryLimit;
public UInt32 JobMemoryLimit;
public UInt32 PeakProcessMemoryUsed;
public UInt32 PeakJobMemoryUsed;
}
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(object a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
private IntPtr m_handle;
private bool m_disposed = false;
public Job()
{
m_handle = CreateJobObject(null, null);
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedInfo.BasicLimitInformation = info;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
private void Dispose(bool disposing)
{
if (m_disposed)
return;
if (disposing) {}
Close();
m_disposed = true;
}
public void Close()
{
Win32.CloseHandle(m_handle);
m_handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr handle)
{
return AssignProcessToJobObject(m_handle, handle);
}
}
Blick auf den Konstruktor ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
Der Schlüssel hier ist, das Jobobjekt richtig einzurichten. Im Konstruktor setze ich die "Grenzen" auf 0x2000, was der numerische Wert für ist JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
MSDN definiert dieses Flag als:
Bewirkt, dass alle mit dem Job verknüpften Prozesse beendet werden, wenn das letzte Handle für den Job geschlossen wird.
Sobald diese Klasse eingerichtet ist, müssen Sie nur noch jeden untergeordneten Prozess beim Job registrieren. Beispielsweise:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
Excel.Application app = new Excel.ApplicationClass();
uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
job.AddProcess(Process.GetProcessById((int)pid).Handle);
Win32.CloseHandle
Ursprung? Wird dies aus kernel32.dll importiert? Dort gibt es eine passende Signatur, aber Sie importieren sie nicht explizit wie die anderen API-Funktionen.Diese Antwort begann mit der hervorragenden Antwort von @Matt Howells und anderen (siehe Links im Code unten). Verbesserungen:
extendedInfoPtr
CreateJobObject
(unter Windows 10, Visual Studio 2015, 32-Bit) erhalten habe.So verwenden Sie diesen Code:
Zur Unterstützung von Windows 7 sind folgende Voraussetzungen erforderlich:
In meinem Fall musste ich Windows 7 nicht unterstützen, daher habe ich oben im statischen Konstruktor unten eine einfache Überprüfung.
Ich habe sowohl die 32-Bit- als auch die 64-Bit-Version der Strukturen sorgfältig getestet, indem ich die verwaltete und die native Version programmgesteuert miteinander verglichen habe (die Gesamtgröße sowie die Offsets für jedes Mitglied).
Ich habe diesen Code unter Windows 7, 8 und 10 getestet.
quelle
Dieser Beitrag ist als Erweiterung der Antwort von @Matt Howells gedacht, insbesondere für diejenigen, die Probleme mit der Verwendung von Jobobjekten unter Vista oder Win7 haben , insbesondere wenn beim Aufrufen von AssignProcessToJobObject der Fehler "Zugriff verweigert" ('5') angezeigt wird.
tl; dr
Fügen Sie dem übergeordneten .NET-Prozess das folgende Manifest hinzu, um die Kompatibilität mit Vista und Win7 sicherzustellen:
Beachten Sie, dass beim Hinzufügen eines neuen Manifests in Visual Studio 2012 das obige Snippet bereits enthalten ist, sodass Sie es nicht aus Hear kopieren müssen. Es wird auch einen Knoten für Windows 8 enthalten.
vollständige Erklärung
Ihre Jobzuordnung schlägt mit einem Fehler "Zugriff verweigert" fehl, wenn der von Ihnen gestartete Prozess bereits einem anderen Job zugeordnet ist. Rufen Sie den Programmkompatibilitäts-Assistenten auf, der ab Windows Vista alle Arten von Prozessen seinen eigenen Jobs zuweist.
In Vista können Sie Ihre Anwendung als von PCA ausgeschlossen markieren, indem Sie einfach ein Anwendungsmanifest einfügen. Visual Studio scheint dies für .NET-Apps automatisch zu tun, sodass Sie dort in Ordnung sind.
Ein einfaches Manifest schneidet es in Win7 nicht mehr. [1] Dort müssen Sie speziell angeben, dass Sie mit Win7 mit dem Tag in Ihrem Manifest kompatibel sind. [2]
Dies führte dazu, dass ich mir Sorgen um Windows 8 machte. Muss ich mein Manifest noch einmal ändern? Anscheinend gibt es eine Unterbrechung in den Clouds, da Windows 8 jetzt zulässt, dass ein Prozess zu mehreren Jobs gehört. [3] Also habe ich es noch nicht getestet, aber ich stelle mir vor, dass dieser Wahnsinn jetzt vorbei sein wird, wenn Sie einfach ein Manifest mit den unterstützten OS-Informationen hinzufügen.
Tipp 1 : Wenn Sie wie ich eine .NET-App mit Visual Studio entwickeln, finden Sie hier [4] einige nützliche Anweisungen zum Anpassen Ihres Anwendungsmanifests.
Tipp 2 : Seien Sie vorsichtig beim Starten Ihrer Anwendung in Visual Studio. Ich stellte fest, dass ich nach dem Hinzufügen des entsprechenden Manifests beim Starten von Visual Studio immer noch Probleme mit PCA hatte, selbst wenn ich Start ohne Debugging verwendet habe. Das Starten meiner Anwendung über den Explorer hat jedoch funktioniert. Nach dem manuellen Hinzufügen von devenv zum Ausschluss von PCA mithilfe der Registrierung funktionierten auch das Starten von Anwendungen, die Jobobjekte von VS verwendeten. [5]
Tipp 3 : Wenn Sie jemals wissen möchten, ob PCA Ihr Problem ist, starten Sie Ihre Anwendung über die Befehlszeile oder kopieren Sie das Programm auf ein Netzwerklaufwerk und führen Sie es von dort aus aus. PCA wird in diesen Kontexten automatisch deaktiviert.
[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an -installer-take-2-weil-wir-die-Regeln-für-dich-geändert haben.aspx
[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant
[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx : "Ein Prozess kann in Windows 8 mehreren Jobs zugeordnet werden."
[4] Wie kann ich mit VS2008 ein Anwendungsmanifest in eine Anwendung einbetten?
[5] Wie kann ich verhindern, dass der Visual Studio-Debugger meinen Prozess in einem Jobobjekt startet?
quelle
Hier ist eine Alternative, die für einige möglicherweise funktioniert, wenn Sie die Kontrolle über den Code haben, den der untergeordnete Prozess ausführt. Der Vorteil dieses Ansatzes besteht darin, dass keine nativen Windows-Aufrufe erforderlich sind.
Die Grundidee besteht darin, die Standardeingabe des Kindes in einen Stream umzuleiten, dessen anderes Ende mit dem übergeordneten Element verbunden ist, und diesen Stream zu verwenden, um zu erkennen, wann das übergeordnete Element verschwunden ist. Wenn Sie das untergeordnete
System.Diagnostics.Process
Element starten, können Sie leicht sicherstellen, dass die Standardeingabe umgeleitet wird:Nutzen Sie dann beim untergeordneten Prozess die Tatsache, dass
Read
s aus dem Standardeingabestream immer mit mindestens 1 Byte zurückgegeben wird, bis der Stream geschlossen wird und dann 0 Byte zurückgegeben werden. Ein Überblick darüber, wie ich dies getan habe, ist unten aufgeführt. Mein Weg verwendet auch eine Nachrichtenpumpe, um den Haupt-Thread für andere Dinge als das Beobachten von Standard in verfügbar zu halten, aber dieser allgemeine Ansatz könnte auch ohne Nachrichtenpumpen verwendet werden.Vorsichtsmaßnahmen für diesen Ansatz:
Die tatsächlich untergeordnete .exe, die gestartet wird, muss eine Konsolenanwendung sein, damit sie an stdin / out / err angehängt bleibt. Wie im obigen Beispiel habe ich meine vorhandene Anwendung, die eine Nachrichtenpumpe verwendete (aber keine GUI zeigte), einfach angepasst, indem ich nur ein kleines Konsolenprojekt erstellt habe, das auf das vorhandene Projekt verweist, meinen Anwendungskontext instanziiert und
Application.Run()
dieMain
Methode von aufgerufen hat Konsole .exe.Technisch gesehen signalisiert dies lediglich den untergeordneten Prozess, wenn der übergeordnete Prozess beendet wird. Es funktioniert also unabhängig davon, ob der übergeordnete Prozess normal beendet wurde oder abgestürzt ist. Es liegt jedoch weiterhin an den untergeordneten Prozessen, das eigene Herunterfahren durchzuführen. Dies kann oder kann nicht sein, was Sie wollen ...
quelle
Eine Möglichkeit besteht darin, die PID des übergeordneten Prozesses an das untergeordnete Element zu übergeben. Das Kind fragt regelmäßig ab, ob der Prozess mit der angegebenen PID vorhanden ist oder nicht. Wenn nicht, wird es einfach beendet.
Sie können die Process.WaitForExit- Methode auch in der untergeordneten Methode verwenden, um benachrichtigt zu werden, wenn der übergeordnete Prozess endet. Im Task-Manager funktioniert dies jedoch möglicherweise nicht.
quelle
Process.Start(string fileName, string arguments)
Es gibt eine andere relevante Methode, einfach und effektiv, um untergeordnete Prozesse beim Beenden des Programms abzuschließen. Sie können einen Debugger vom übergeordneten Element implementieren und an ihn anhängen . Wenn der übergeordnete Prozess endet, werden untergeordnete Prozesse vom Betriebssystem beendet. Es kann in beide Richtungen gehen, indem ein Debugger vom Kind an das übergeordnete Element angehängt wird (beachten Sie, dass Sie jeweils nur einen Debugger anhängen können). Weitere Informationen zu diesem Thema finden Sie hier .
Hier haben Sie eine Dienstprogrammklasse, die einen neuen Prozess startet und einen Debugger an ihn anfügt. Es wurde aus diesem Beitrag von Roger Knapp angepasst . Die einzige Voraussetzung ist, dass beide Prozesse die gleiche Bitness haben müssen. Sie können einen 32-Bit-Prozess nicht von einem 64-Bit-Prozess debuggen oder umgekehrt.
Verwendung:
quelle
Ich suchte nach einer Lösung für dieses Problem, für die kein nicht verwalteter Code erforderlich war. Ich konnte auch keine Standard-Eingabe- / Ausgabeumleitung verwenden, da es sich um eine Windows Forms-Anwendung handelte.
Meine Lösung bestand darin, im übergeordneten Prozess eine Named Pipe zu erstellen und dann den untergeordneten Prozess mit derselben Pipe zu verbinden. Wenn der übergeordnete Prozess beendet wird, wird die Pipe unterbrochen und das untergeordnete Element kann dies erkennen.
Unten sehen Sie ein Beispiel mit zwei Konsolenanwendungen:
Elternteil
Kind
quelle
Verwenden Sie Ereignishandler , um Hooks für einige Exit-Szenarien zu erstellen:
quelle
Nur meine Version 2018. Verwenden Sie es neben Ihrer Main () -Methode.
quelle
Ich sehe zwei Möglichkeiten:
quelle
Ich habe eine untergeordnete Prozessverwaltungsbibliothek erstellt, in der der übergeordnete Prozess und der untergeordnete Prozess aufgrund einer bidirektionalen WCF-Pipe überwacht werden. Wenn entweder der untergeordnete Prozess oder der übergeordnete Prozess sich gegenseitig beendet, wird dies benachrichtigt. Es ist auch ein Debugger-Helfer verfügbar, der den VS-Debugger automatisch an den gestarteten untergeordneten Prozess anfügt
Projektseite:
http://www.crawler-lib.net/child-processes
NuGet-Pakete:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
quelle
Rufen Sie job.AddProcess nach dem Start des Prozesses besser auf:
Wenn Sie AddProcess vor dem Beenden aufrufen, werden untergeordnete Prozesse nicht beendet. (Windows 7 SP1)
quelle
Noch eine Ergänzung zu der Fülle der bisher vorgeschlagenen Lösungen ....
Das Problem bei vielen von ihnen ist, dass sie sich darauf verlassen, dass der Eltern- und Kinderprozess ordnungsgemäß heruntergefahren wird, was bei der Entwicklung nicht immer der Fall ist. Ich stellte fest, dass mein untergeordneter Prozess häufig verwaist war, wenn ich den übergeordneten Prozess im Debugger beendete. Daher musste ich die verwaisten Prozesse mit dem Task-Manager beenden, um meine Lösung neu zu erstellen.
Die Lösung: Übergeben Sie die übergeordnete Prozess-ID in der Befehlszeile (oder noch weniger invasiv in den Umgebungsvariablen) des untergeordneten Prozesses.
Im übergeordneten Prozess ist die Prozess-ID verfügbar als:
Im untergeordneten Prozess:
Ich bin mir nicht sicher, ob dies im Produktionscode akzeptabel ist. Einerseits sollte dies niemals passieren. Andererseits kann dies den Unterschied zwischen dem Neustart eines Prozesses und dem Neustart eines Produktionsservers bedeuten. Und was niemals passieren sollte, tut es oft.
Und es ist sicher nützlich, wenn Sie Probleme beim ordnungsgemäßen Herunterfahren beheben.
quelle