web-dev-qa-db-fra.com

Tuer le processus enfant lorsque le processus parent est tué

Je crée de nouveaux processus en utilisant System.Diagnostics.Process classe de mon application.

Je souhaite que ces processus soient arrêtés lorsque/si mon application s’est écrasée. Mais si je tue mon application à partir du Gestionnaire des tâches, les processus enfants ne sont pas tués.

Existe-t-il un moyen de rendre les processus enfants dépendants du processus parent?

143
SiberianGuy

De ce forum , crédit à 'Josh'.

Application.Quit() et Process.Kill() sont des solutions possibles, mais se sont révélés peu fiables. Lorsque votre application principale meurt, il reste des processus enfants 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 travail. Dans le constructeur, je règle les "limites" sur 0x2000, qui est la valeur numérique pour JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN définit cet indicateur comme suit:

Tous les processus associés au travail sont arrêtés 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);
160
Matt Howells

Ce message est destiné à être une extension de la réponse de @Matt Howells, en particulier à ceux qui rencontrent des problèmes lors de l’utilisation de Job Objects sous Vista ou Win7 , en particulier si vous obtenez une erreur d'accès refusé ('5') lorsque vous appelez AssignProcessToJobObject.

tl; dr

Pour assurer la compatibilité avec Vista et Win7, ajoutez le manifeste suivant au processus parent .NET:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Assembly xmlns="urn:schemas-Microsoft-com:asm.v1" manifestVersion="1.0">
  <v3:trustInfo xmlns:v3="urn:schemas-Microsoft-com:asm.v3">
    <v3:security>
      <v3:requestedPrivileges>
        <v3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </v3:requestedPrivileges>
    </v3:security>
  </v3:trustInfo>
  <compatibility xmlns="urn:schemas-Microsoft-com:compatibility.v1">
    <!-- We specify these, in addition to the UAC above, so we avoid Program Compatibility Assistant in Vista and Win7 -->
    <!-- We try to avoid PCA so we can use Windows Job Objects -->
    <!-- See https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed -->

    <application>
      <!--The ID below indicates application support for Windows Vista -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!--The ID below indicates application support for Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
    </application>
  </compatibility>
</Assembly>

Notez que lorsque vous ajoutez un nouveau manifeste dans Visual Studio 2012, celui-ci 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 au travail échouera avec une erreur d’accès refusé si le processus que vous démarrez est déjà associé à un autre travail. Accédez à l'Assistant Compatibilité des programmes qui, à partir de Windows Vista, affectera toutes sortes de processus à ses propres travaux.

Dans Vista, vous pouvez indiquer que votre application est exclue de la PCA en incluant simplement un manifeste d'application. Visual Studio semble le faire 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. Devrai-je modifier à nouveau mon manifeste? Apparemment, il y a une rupture dans les nuages, car Windows 8 permet désormais à un processus d'appartenir à plusieurs travaux. [3] Donc, je ne l'ai 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 parOS.

Astuce 1 : Si vous développez une application .NET avec Visual Studio, comme moi, voici [4] quelques instructions utiles sur la personnalisation. votre manifeste d'application.

Astuce 2 : faites attention 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 à partir de l'explorateur a cependant fonctionné. Après avoir ajouté manuellement devenv pour exclure PCA à l'aide du registre, le démarrage des applications utilisant des objets de travail de VS s'est également mis à fonctionner. [5]

Astuce 3 : Si vous voulez savoir si votre problème est PCA, essayez de lancer votre application à partir de la ligne de commande ou copiez le programme sur un lecteur réseau. et l'exécuter à 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-to-tell-us-you-are -not-un-installateur-prendre-2-parce-que-nous-avons-changé-les-règles-sur-vous.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 de un travail sous Windows 8 "

[4] Comment puis-je incorporer un manifeste d'application dans une application utilisant VS2008?

[5] Comment arrêter le débogueur Visual Studio de démarrer mon processus dans un objet de travail?

45
adam smith

Cette réponse a commencé par l'excellente réponse de @ Matt Howells plus d'autres (voir les liens dans le code ci-dessous). Améliorations:

  • Prend en charge 32 bits et 64 bits.
  • Corrige quelques problèmes dans la réponse de @Matt Howells:
    1. La petite fuite de mémoire de extendedInfoPtr
    2. L'erreur de compilation 'Win32', et
    3. Une exception de pile non équilibrée que j'ai eu lors de l'appel à CreateJobObject (sous Windows 10, Visual Studio 2015, 32 bits).
  • Nomme le travail. Ainsi, si vous utilisez SysInternals, par exemple, vous pourrez le trouver facilement.
  • A une API un peu plus simple et moins de code.

Voici comment utiliser ce code:

// Get a Process object somehow.
Process process = Process.Start(exePath, args);
// Add the Process to ChildProcessTracker.
ChildProcessTracker.AddProcess(process);

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 vérification simple en haut du constructeur statique ci-dessous.

/// <summary>
/// Allows processes to be automatically killed if this parent process unexpectedly quits.
/// This feature requires Windows 8 or greater. On Windows 7, nothing is done.</summary>
/// <remarks>References:
///  https://stackoverflow.com/a/4657392/386091
///  https://stackoverflow.com/a/9164742/386091 </remarks>
public static class ChildProcessTracker
{
    /// <summary>
    /// Add the process to be tracked. If our current process is killed, the child processes
    /// that we are tracking will be automatically killed, too. If the child process terminates
    /// first, that's fine, too.</summary>
    /// <param name="process"></param>
    public static void AddProcess(Process process)
    {
        if (s_jobHandle != IntPtr.Zero)
        {
            bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
            if (!success && !process.HasExited)
                throw new Win32Exception();
        }
    }

    static ChildProcessTracker()
    {
        // This feature requires Windows 8 or later. To support Windows 7 requires
        //  registry settings to be added if you are using Visual Studio plus an
        //  app.manifest change.
        //  https://stackoverflow.com/a/4232259/386091
        //  https://stackoverflow.com/a/9507862/386091
        if (Environment.OSVersion.Version < new Version(6, 2))
            return;

        // The job name is optional (and can be null) but it helps with diagnostics.
        //  If it's not null, it has to be unique. Use SysInternals' Handle command-line
        //  utility: handle -a ChildProcessTracker
        string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
        s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);

        var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();

        // This is the key flag. When our process is killed, Windows will automatically
        //  close the job handle, and when that happens, we want the child processes to
        //  be killed, too.
        info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

        var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        try
        {
            Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

            if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
                extendedInfoPtr, (uint)length))
            {
                throw new Win32Exception();
            }
        }
        finally
        {
            Marshal.FreeHGlobal(extendedInfoPtr);
        }
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
        IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    // Windows will automatically close any open job handles when our process terminates.
    //  This can be verified by using SysInternals' Handle utility. When the job handle
    //  is closed, the child processes will be killed.
    private static readonly IntPtr s_jobHandle;
}

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public JOBOBJECTLIMIT LimitFlags;
    public UIntPtr MinimumWorkingSetSize;
    public UIntPtr MaximumWorkingSetSize;
    public UInt32 ActiveProcessLimit;
    public Int64 Affinity;
    public UInt32 PriorityClass;
    public UInt32 SchedulingClass;
}

[Flags]
public enum JOBOBJECTLIMIT : uint
{
    JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
}

[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UIntPtr ProcessMemoryLimit;
    public UIntPtr JobMemoryLimit;
    public UIntPtr PeakProcessMemoryUsed;
    public UIntPtr PeakJobMemoryUsed;
}

J'ai soigneusement testé les versions 32 bits et 64 bits des structures en comparant par programme les versions gérée et native (la taille globale ainsi que les décalages de chaque membre).

J'ai testé ce code sous Windows 7, 8 et 10.

43
Ron

Voici une alternative qui peut fonctionner pour certains lorsque vous avez le contrôle du 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 la disparition du parent. 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:

Process childProcess = new Process();
childProcess.StartInfo = new ProcessStartInfo("pathToConsoleModeApp.exe");
childProcess.StartInfo.RedirectStandardInput = true;

childProcess.StartInfo.CreateNoWindow = true; // no sense showing an empty black console window which the user can't input into

Et puis, sur le processus enfant, tirez parti du fait que Reads du flux d'entrée standard sera toujours renvoyé avec au moins 1 octet jusqu'à ce que le flux soit fermé, où ils commenceront à renvoyer 0 octet. Voici un aperçu de la façon dont j'ai fini par le faire. Mon chemin utilise également une pompe de messages pour garder le fil principal disponible pour des choses autres que l'observation standard, mais cette approche générale pourrait également être utilisée sans pompes de messages.

using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;

static int Main()
{
    Application.Run(new MyApplicationContext());
    return 0;
}

public class MyApplicationContext : ApplicationContext
{
    private SynchronizationContext _mainThreadMessageQueue = null;
    private Stream _stdInput;

    public MyApplicationContext()
    {
        _stdInput = Console.OpenStandardInput();

        // feel free to use a better way to post to the message loop from here if you know one ;)    
        System.Windows.Forms.Timer handoffToMessageLoopTimer = new System.Windows.Forms.Timer();
        handoffToMessageLoopTimer.Interval = 1;
        handoffToMessageLoopTimer.Tick += new EventHandler((obj, eArgs) => { PostMessageLoopInitialization(handoffToMessageLoopTimer); });
        handoffToMessageLoopTimer.Start();
    }

    private void PostMessageLoopInitialization(System.Windows.Forms.Timer t)
    {
        if (_mainThreadMessageQueue == null)
        {
            t.Stop();
            _mainThreadMessageQueue = SynchronizationContext.Current;
        }

        // constantly monitor standard input on a background thread that will
        // signal the main thread when stuff happens.
        BeginMonitoringStdIn(null);

        // start up your application's real work here
    }

    private void BeginMonitoringStdIn(object state)
    {
        if (SynchronizationContext.Current == _mainThreadMessageQueue)
        {
            // we're already running on the main thread - proceed.
            var buffer = new byte[128];

            _stdInput.BeginRead(buffer, 0, buffer.Length, (asyncResult) =>
                {
                    int amtRead = _stdInput.EndRead(asyncResult);

                    if (amtRead == 0)
                    {
                        _mainThreadMessageQueue.Post(new SendOrPostCallback(ApplicationTeardown), null);
                    }
                    else
                    {
                        BeginMonitoringStdIn(null);
                    }
                }, null);
        }
        else
        {
            // not invoked from the main thread - dispatch another call to this method on the main thread and return
            _mainThreadMessageQueue.Post(new SendOrPostCallback(BeginMonitoringStdIn), null);
        }
    }

    private void ApplicationTeardown(object state)
    {
        // tear down your application gracefully here
        _stdInput.Close();

        this.ExitThread();
    }
}

Mises en garde à cette approche:

  1. le fichier .exe réellement lancé doit être une application console, de sorte qu'il reste attaché à stdin/out/err. Comme dans l'exemple ci-dessus, j'ai facilement adapté mon application existante qui utilisait une pompe à messages (mais ne montrait pas d'interface graphique) en créant simplement un petit projet de console référençant le projet existant, en instanciant le contexte de mon application et en appelant Application.Run() dans la méthode Main de la console .exe.

  2. Techniquement, cela ne fait que signale le processus enfant lorsque le parent se ferme, ainsi le système fonctionnera normalement, qu'il soit arrêté normalement ou s'il est bloqué, mais qu'il incombe toujours aux processus enfants d'effectuer leur propre arrêt. Cela peut ou peut ne pas être ce que vous voulez ...

16
mbaynton

Une solution 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 va simplement arrêter.

Vous pouvez également utiliser la méthode Process.WaitForExit dans la méthode enfant pour être averti de la fin du processus parent, mais cette opération risque de ne pas fonctionner dans le cas du Gestionnaire des tâches.

9
Giorgi

Il existe une autre méthode pertinente, simple et efficace, pour terminer les processus enfants à la fin du programme. Vous pouvez implémenter et attacher un débogueur depuis le parent; à la fin du processus parent, les processus enfants seront supprimés par le système d'exploitation. Cela peut aller dans les deux sens en attachant un débogueur au parent depuis l'enfant (notez que vous ne pouvez attacher qu'un seul débogueur à la fois). Vous pouvez trouver plus d'informations sur le sujet ici .

Ici, vous avez une classe d’utilitaires qui lance un nouveau processus et y attache un débogueur. Il a été adapté de this post par Roger Knapp. La seule exigence est que les deux processus doivent partager le même débit binaire. Vous ne pouvez pas déboguer un processus 32 bits à partir d'un processus 64 bits ou inversement.

public class ProcessRunner
{
    #region "API imports"

    private const int DBG_CONTINUE = 0x00010002;
    private const int DBG_EXCEPTION_NOT_HANDLED = unchecked((int) 0x80010001);

    private enum DebugEventType : int
    {
        CREATE_PROCESS_DEBUG_EVENT = 3,
        //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
        CREATE_THREAD_DEBUG_EVENT = 2,
        //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
        EXCEPTION_DEBUG_EVENT = 1,
        //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
        EXIT_PROCESS_DEBUG_EVENT = 5,
        //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
        EXIT_THREAD_DEBUG_EVENT = 4,
        //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
        LOAD_DLL_DEBUG_EVENT = 6,
        //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
        OUTPUT_DEBUG_STRING_EVENT = 8,
        //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
        RIP_EVENT = 9,
        //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
        UNLOAD_DLL_DEBUG_EVENT = 7,
        //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct DEBUG_EVENT
    {
        [MarshalAs(UnmanagedType.I4)] public DebugEventType dwDebugEventCode;
        public int dwProcessId;
        public int dwThreadId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public byte[] bytes;
    }

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool DebugActiveProcess(int dwProcessId);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);

    [DllImport("Kernel32.dll", SetLastError = true)]
    private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern bool IsDebuggerPresent();

    #endregion

    public Process ChildProcess { get; set; }

    public bool StartProcess(string fileName)
    {
        var processStartInfo = new ProcessStartInfo(fileName)
        {
            UseShellExecute = false,
            WindowStyle = ProcessWindowStyle.Normal,
            ErrorDialog = false
        };

        this.ChildProcess = Process.Start(processStartInfo);
        if (ChildProcess == null)
            return false;

        new Thread(NullDebugger) {IsBackground = true}.Start(ChildProcess.Id);
        return true;
    }

    private void NullDebugger(object arg)
    {
        // Attach to the process we provided the thread as an argument
        if (DebugActiveProcess((int) arg))
        {
            var debugEvent = new DEBUG_EVENT {bytes = new byte[1024]};
            while (!this.ChildProcess.HasExited)
            {
                if (WaitForDebugEvent(out debugEvent, 1000))
                {
                    // return DBG_CONTINUE for all events but the exception type
                    var continueFlag = DBG_CONTINUE;
                    if (debugEvent.dwDebugEventCode == DebugEventType.EXCEPTION_DEBUG_EVENT)
                        continueFlag = DBG_EXCEPTION_NOT_HANDLED;
                    ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueFlag);
                }
            }
        }
        else
        {
            //we were not able to attach the debugger
            //do the processes have the same bitness?
            //throw ApplicationException("Unable to attach debugger") // Kill child? // Send Event? // Ignore?
        }
    }
}

Usage:

    new ProcessRunner().StartProcess("c:\\Windows\\system32\\calc.exe");
7
Marco Regueira

Je cherchais une solution à ce problème ne nécessitant pas de code non géré. Je ne pouvais pas non plus utiliser la redirection d'entrée/sortie standard car il s'agissait d'une application Windows Forms.

Ma solution consistait à créer un canal nommé dans le processus parent, puis à connecter le processus enfant au même canal. Si le processus parent se ferme, le canal devient rompu et l'enfant peut le détecter.

Vous trouverez ci-dessous un exemple utilisant deux applications de console:

Parent

private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529";

public static void Main(string[] args)
{
    Console.WriteLine("Main program running");

    using (NamedPipeServerStream pipe = new NamedPipeServerStream(PipeName, PipeDirection.Out))
    {
        Process.Start("child.exe");

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

Enfant

private const string PipeName = "471450d6-70db-49dc-94af-09d3f3eba529"; // same as parent

public static void Main(string[] args)
{
    Console.WriteLine("Child process running");

    using (NamedPipeClientStream pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.In))
    {
        pipe.Connect();
        pipe.BeginRead(new byte[1], 0, 1, PipeBrokenCallback, pipe);

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

private static void PipeBrokenCallback(IAsyncResult ar)
{
    // the pipe was closed (parent process died), so exit the child process too

    try
    {
        NamedPipeClientStream pipe = (NamedPipeClientStream)ar.AsyncState;
        pipe.EndRead(ar);
    }
    catch (IOException) { }

    Environment.Exit(1);
}
3
Alsty

Utilisez gestionnaires d'événements pour créer des points d'ancrage sur quelques scénarios de sortie:

var process = Process.Start("program.exe");
AppDomain.CurrentDomain.DomainUnload += (s, e) => { process.Kill(); process.WaitForExit(); };
AppDomain.CurrentDomain.ProcessExit += (s, e) => { process.Kill(); process.WaitForExit(); };
AppDomain.CurrentDomain.UnhandledException += (s, e) => { process.Kill(); process.WaitForExit(); };
2
Justin Harris

Juste ma version 2018. Utilisez-le à côté de votre méthode Main ().

    using System.Management;
    using System.Diagnostics;

    ...

    // Called when the Main Window is closed
    protected override void OnClosed(EventArgs EventArgs)
    {
        string query = "Select * From Win32_Process Where ParentProcessId = " + Process.GetCurrentProcess().Id;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
        ManagementObjectCollection processList = searcher.Get();
        foreach (var obj in processList)
        {
            object data = obj.Properties["processid"].Value;
            if (data != null)
            {
                // retrieve the process
                var childId = Convert.ToInt32(data);
                var childProcess = Process.GetProcessById(childId);

                // ensure the current process is still live
                if (childProcess != null) childProcess.Kill();
            }
        }
        Environment.Exit(0);
    }
2
Lenor

Je vois deux options:

  1. Si vous savez exactement quel processus enfant peut être démarré et que vous êtes certain qu'il ne s'agit que de votre processus principal, vous pouvez envisager de simplement les rechercher par leur nom et de les tuer.
  2. Parcourez tous les processus et supprimez tous les processus associés à votre processus en tant que parent (je suppose que vous devez d'abord supprimer les processus enfants). Ici explique comment vous pouvez obtenir l'ID du processus parent.
1
Stefan Egli

J'ai créé une bibliothèque de gestion de processus enfant dans laquelle 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, il en est informé. Il existe également un assistant de débogage disponible qui attache automatiquement le débogueur du VS au processus enfant démarré.

Site du projet:

http://www.crawler-lib.net/child-processes

NuGet Packages:

https://www.nuget.org/packages/ChildProcesseshttps://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/

1

appelez job.AddProcess mieux à faire après le début du processus:

prc.Start();
job.AddProcess(prc.Handle);

Lorsque vous appelez AddProcess avant la fin, les processus enfants ne sont pas supprimés. (Windows 7 SP1)

private void KillProcess(Process proc)
{
    var job = new Job();
    job.AddProcess(proc.Handle);
    job.Close();
}
0
Alexey