De ce forum , crédit à «Josh».
Application.Quit()
et Process.Kill()
sont des solutions possibles, mais se sont avérées peu fiables. Lorsque votre application principale meurt, les processus enfants sont toujours en cours d'exécution. Ce que nous voulons vraiment, c'est que les processus enfants meurent dès que le processus principal meurt.
La solution consiste à utiliser des "objets de travail" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
L'idée est de créer un "objet de travail" pour votre application principale et d'enregistrer vos processus enfants avec l'objet de travail. Si le processus principal meurt, le système d'exploitation se chargera de mettre fin aux processus enfants.
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);
}
}
En regardant le constructeur ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
La clé ici est de configurer correctement l'objet de travail. Dans le constructeur, je mets les "limites" à 0x2000, qui est la valeur numérique de JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
MSDN définit cet indicateur comme:
Provoque l'arrêt de tous les processus associés au travail lorsque le dernier descripteur du travail est fermé.
Une fois cette classe configurée ... il vous suffit d'enregistrer chaque processus enfant avec le travail. Par exemple:
[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
vient-il? Est-ce importé de kernel32.dll? Il y a une signature correspondante ici, mais vous ne l'importez pas explicitement comme les autres fonctions de l'API.Cette réponse a commencé par l'excellente réponse de @Matt Howells et d'autres (voir les liens dans le code ci-dessous). Améliorations:
extendedInfoPtr
CreateJobObject
(à l'aide de Windows 10, Visual Studio 2015, 32 bits).Voici comment utiliser ce code:
Pour prendre en charge Windows 7, il faut:
Dans mon cas, je n'avais pas besoin de prendre en charge Windows 7, j'ai donc une simple vérification en haut du constructeur statique ci-dessous.
J'ai soigneusement testé les versions 32 bits et 64 bits des structures en comparant par programmation les versions gérées et natives les unes aux autres (la taille globale ainsi que les décalages pour chaque membre).
J'ai testé ce code sur Windows 7, 8 et 10.
la source
Cet article est conçu comme une extension de la réponse de @Matt Howells, en particulier pour ceux qui rencontrent des problèmes avec l'utilisation des objets de travail sous Vista ou Win7 , en particulier si vous obtenez une erreur d'accès refusé ('5') lors de l'appel à AssignProcessToJobObject.
tl; dr
Pour garantir la compatibilité avec Vista et Win7, ajoutez le manifeste suivant au processus parent .NET:
Notez que lorsque vous ajoutez un nouveau manifeste dans Visual Studio 2012, il contient déjà l'extrait de code ci-dessus, vous n'avez donc pas besoin de le copier à partir de hear. Il comprendra également un nœud pour Windows 8.
explication complète
Votre association de travail échouera avec une erreur d'accès refusé si le processus que vous démarrez est déjà associé à un autre travail. Entrez l'assistant de compatibilité des programmes, qui, à partir de Windows Vista, attribuera toutes sortes de processus à ses propres travaux.
Dans Vista, vous pouvez marquer votre application comme étant exclue de PCA en incluant simplement un manifeste d'application. Visual Studio semble faire cela automatiquement pour les applications .NET, donc tout va bien.
Un simple manifeste ne le coupe plus dans Win7. [1] Là, vous devez spécifier spécifiquement que vous êtes compatible avec Win7 avec la balise dans votre manifeste. [2]
Cela m'a amené à m'inquiéter pour Windows 8. Dois-je à nouveau changer mon manifeste? Apparemment, il y a une rupture dans les nuages, car Windows 8 permet désormais à un processus d'appartenir à plusieurs tâches. [3] Je ne l'ai donc pas encore testé, mais j'imagine que cette folie sera terminée maintenant si vous incluez simplement un manifeste avec les informations prises en charge par OS.
Astuce 1 : Si vous développez une application .NET avec Visual Studio, comme je l'étais, voici [4] quelques instructions intéressantes sur la façon de personnaliser votre manifeste d'application.
Astuce 2 : Soyez prudent lorsque vous lancez votre application à partir de Visual Studio. J'ai constaté qu'après avoir ajouté le manifeste approprié, j'avais toujours des problèmes avec PCA lors du lancement à partir de Visual Studio, même si j'utilisais Démarrer sans débogage. Le lancement de mon application depuis Explorer a cependant fonctionné. Après avoir ajouté manuellement devenv pour l'exclusion de PCA à l'aide du registre, le démarrage des applications utilisant des objets de travail de VS a également commencé à fonctionner. [5]
Astuce 3 : Si vous voulez savoir si PCA est votre problème, essayez de lancer votre application à partir de la ligne de commande ou copiez le programme sur un lecteur réseau et exécutez-le à partir de là. PCA est automatiquement désactivé dans ces contextes.
[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-car-we-change-the-rules-on-you.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 : "Un processus peut être associé à plus d'un travail dans Windows 8"
[4] Comment puis-je intégrer un manifeste d'application dans une application à l'aide de VS2008?
[5] Comment arrêter le débogueur Visual Studio en démarrant mon processus dans un objet de travail?
la source
Voici une alternative qui peut fonctionner pour certains lorsque vous contrôlez le code exécuté par le processus enfant. L'avantage de cette approche est qu'elle ne nécessite aucun appel Windows natif.
L'idée de base est de rediriger l'entrée standard de l'enfant vers un flux dont l'autre extrémité est connectée au parent, et d'utiliser ce flux pour détecter quand le parent est parti. Lorsque vous utilisez
System.Diagnostics.Process
pour démarrer l'enfant, il est facile de s'assurer que son entrée standard est redirigée:Et puis, sur le processus enfant, profitez du fait que
Read
s du flux d'entrée standard retournera toujours avec au moins 1 octet jusqu'à ce que le flux soit fermé, quand ils commenceront à renvoyer 0 octet. Un aperçu de la façon dont j'ai fini par le faire est ci-dessous; my way utilise également une pompe de message pour garder le fil principal disponible pour des choses autres que regarder la norme, mais cette approche générale pourrait également être utilisée sans pompes de message.Mises en garde à cette approche:
le fichier .exe enfant réel qui est lancé doit être une application console afin qu'il reste attaché à stdin / out / err. Comme dans l'exemple ci-dessus, j'ai facilement adapté mon application existante qui utilisait une pompe de message (mais n'a pas montré d'interface graphique) en créant simplement un petit projet de console qui faisait référence au projet existant, en instanciant mon contexte d'application et en appelant
Application.Run()
à l'intérieur de laMain
méthode du console .exe.Techniquement, cela signale simplement le processus enfant lorsque le parent se termine, donc cela fonctionnera si le processus parent s'est terminé normalement ou s'est écrasé, mais il appartient toujours aux processus enfants d'effectuer leur propre arrêt. C'est peut-être ce que vous voulez ou non ...
la source
Une façon consiste à transmettre le PID du processus parent à l'enfant. L'enfant interrogera périodiquement si le processus avec le pid spécifié existe ou non. Sinon, il s'arrêtera.
Vous pouvez également utiliser la méthode Process.WaitForExit dans la méthode enfant pour être averti lorsque le processus parent se termine, mais cela peut ne pas fonctionner dans le cas du Gestionnaire des tâches.
la source
Process.Start(string fileName, string arguments)
Il existe une autre méthode pertinente, simple et efficace, pour terminer les processus enfants à la fin du programme. Vous pouvez implémenter et leur attacher un débogueur à partir du parent; lorsque le processus parent se termine, les processus enfants seront tués par le système d'exploitation. Il peut aller dans les deux sens en attachant un débogueur au parent de l'enfant (notez que vous ne pouvez attacher qu'un débogueur à la fois). Vous pouvez trouver plus d'informations sur le sujet ici .
Ici, vous avez une classe utilitaire qui lance un nouveau processus et y attache un débogueur. Il a été adapté de ce billet par Roger Knapp. La seule exigence est que les deux processus doivent partager le même témoin. Vous ne pouvez pas déboguer un processus 32 bits à partir d'un processus 64 bits ou vice versa.
Usage:
la source
Je cherchais une solution à ce problème qui ne nécessitait pas de code non managé. Je n'étais pas non plus en mesure d'utiliser la redirection d'entrée / sortie standard car il s'agissait d'une application Windows Forms.
Ma solution était de créer un tube nommé dans le processus parent, puis de connecter le processus enfant au même tube. Si le processus parent se termine, le canal est rompu et l'enfant peut le détecter.
Voici un exemple utilisant deux applications de console:
Parent
Enfant
la source
Utilisez des gestionnaires d'événements pour créer des hooks sur quelques scénarios de sortie:
la source
Juste ma version 2018. Utilisez-le en dehors de votre méthode Main ().
la source
Je vois deux options:
la source
J'ai créé une bibliothèque de gestion de processus enfant où le processus parent et le processus enfant sont surveillés grâce à un canal WCF bidirectionnel. Si le processus enfant se termine ou si le processus parent se termine, l'un l'autre est notifié. Il existe également un assistant de débogage disponible qui attache automatiquement le débogueur VS au processus enfant démarré
Site du projet:
http://www.crawler-lib.net/child-processes
Packages NuGet:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
la source
appelez job.AddProcess est préférable de faire après le démarrage du processus:
Lorsque vous appelez AddProcess avant la fin, les processus enfants ne sont pas tués. (Windows 7 SP1)
la source
Encore un ajout à l'abondante richesse des solutions proposées jusqu'à présent ...
Le problème avec beaucoup d'entre eux est qu'ils comptent sur le processus parent et enfant pour s'arrêter de manière ordonnée, ce qui n'est pas toujours vrai lorsque le développement est en cours. J'ai constaté que mon processus enfant était souvent orphelin chaque fois que je terminais le processus parent dans le débogueur, ce qui m'obligeait à tuer le ou les processus orphelins avec Task Manager afin de reconstruire ma solution.
La solution: transmettez l'ID du processus parent sur la ligne de commande (ou encore moins invasive, dans les variables d'environnement) du processus enfant.
Dans le processus parent, l'ID de processus est disponible sous la forme:
Dans le processus enfant:
Je ne vois pas si c'est une pratique acceptable dans le code de production. D'une part, cela ne devrait jamais arriver. Mais d'un autre côté, cela peut faire la différence entre le redémarrage d'un processus et le redémarrage d'un serveur de production. Et ce qui ne devrait jamais arriver le fait souvent.
Et c'est certainement utile lors du débogage des problèmes d'arrêt ordonné.
la source