171 votes

Tuer processus enfant lors de processus parent est mort

J’ai créé des nouveaux processus à l’aide de la classe System.Diagnostics.Process de mon application. Je veux que ce processus pour être tué quand / si ma demande est tombé en panne. Mais si je tue mon application de gestionnaire des tâches, les processus enfants ne sont pas tués. Est-il possible de rendre les processus enfants dépendant de processus parent ?

183voto

Matt Howells Points 20751

À partir de ce forum, de crédit à Josh'.

Application.Quit() et Process.Kill() sont des solutions possibles, mais ont prouvé pour être peu fiables. Lorsque votre application principale meurt, vous êtes toujours à gauche avec enfant des processus en cours d'exécution. Ce que nous voulons vraiment, c'est pour l'enfant le processus de mourir dès que le processus principal meurt.

La solution est d'utiliser des "objets" http://msdn.microsoft.com/en-us/library/ms682409(SV.85).aspx.

L'idée est de créer un "objet de travail" pour votre application principale, et d'inscrire votre enfant des processus avec l'objet de la tâche. Si le processus principal meurt, l'OS va prendre soin de terminer le processus enfant.

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 l'objet de la tâche correctement. Dans le constructeur, je suis à la définition de la "limite" à 0x2000, qui est la valeur numérique JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN définit cet indicateur comme:

Les Causes de tous les processus associés le travail de fin lorsque le dernier poignée pour l'emploi est fermé.

Une fois cette classe est le programme d'installation...vous avez juste à vous inscrire à chaque enfant de processus 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);

48voto

adam smith Points 421

Ce poste est conçu comme une extension de @Matt Howells " réponse, en particulier pour ceux qui rencontrent des problèmes avec l'aide d'Emploi d'Objets sous Vista ou Win7, surtout si vous obtenez une erreur accès refusé ('5') lors de l'appel de AssignProcessToJobObject.

tl;dr

Pour assurer la compatibilité avec Vista et Win7, ajoutez les éléments suivants manifeste à l' .NET processus parent:

<?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 http://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 de nouveaux manifeste dans Visual Studio 2012, elle contient de l'extrait ci-dessus, vous n'avez pas besoin de les copier à partir d'entendre. Il comprend également un nœud pour Windows 8.

explication complète

Votre travail de l'association échoue avec une erreur d'accès refusé si le processus que vous êtes de départ est déjà associé à un autre emploi. Entrez dans l'Assistant Compatibilité des programmes, qui, à partir de Windows Vista, va affecter tous les types de processus de son propre emploi.

Dans Vista, vous pouvez marquer votre demande à être exclus de l'APC simplement en incluant un manifeste de l'application. Visual Studio semble pour .NET applications automatiquement, de sorte que vous êtes bien là.

Un simple manifeste plus des coupes dans Win7. [1] Là, vous devez expressément indiquer que vous êtes compatible avec Win7 avec la balise dans votre manifeste. [2]

Cela m'a conduit à vous inquiéter au sujet de Windows 8. Vais-je devoir changer mon manifeste une fois de plus? Apparemment il y a une pause dans les nuages, comme Windows 8 maintenant, permet à un processus d'appartenir à plusieurs travaux. [3] Donc, je n'ai pas encore testé, mais j'imagine que cette folie sera plus maintenant, si vous incluez simplement un manifeste avec la supportedOS de l'information.

Astuce 1: Si vous êtes l'élaboration d'une .NET application avec Visual Studio, comme je l'ai été, ici [4] sont quelques belles instructions sur la façon de personnaliser votre manifeste de l'application.

Astuce 2: Être prudent avec le lancement de votre application à partir de Visual Studio. J'ai trouvé que, après l'ajout de l'manifeste approprié, j'ai toujours eu des problèmes avec l'APC lors du lancement de Visual Studio, même si j'ai utilisé exécuter sans Débogage. Le lancement de mon application à partir de l'Explorateur travaillé, cependant. Après l'ajout manuel d'devenv pour l'exclusion de l'APC à l'aide de la base de registre, les applications de démarrage que l'habitude du Travail Objets à partir de VS a commencé à travailler ainsi. [5]

Astuce 3: Si jamais vous voulez savoir si l'APC est votre problème, essayez de lancer votre application à partir de la ligne de commande, ou de copier le programme sur un lecteur réseau et exécuter à partir de là. L'APC 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-because-we-changed-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 qui peut être associé à plus d'un emploi dans Windows 8"

[4] Comment puis-je intégrer un manifeste de l'application dans une application à l'aide de VS2008?

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

16voto

mbaynton Points 71

Voici une alternative qui peut fonctionner pour certains, quand vous avez le contrôle du code de l'enfant, le processus s'exécute. L'avantage de cette approche est qu'elle ne nécessite pas natif de Windows appels.

L'idée de base est de rediriger l'enfant de l'entrée standard d'un ruisseau dont l'autre extrémité est connectée à la mère, et utilisez ce flux de données pour détecter le moment où la mère a disparu. Lorsque vous utilisez System.Diagnostics.Process pour démarrer l'enfant, il est facile de s'assurer de 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 de l'enfant, de profiter du fait qu' Reads à partir du flux d'entrée standard retournera toujours avec au moins 1 octet jusqu'à ce que le flux est fermé, quand ils commencent à retourner 0 octets. Un aperçu de la façon dont j'ai fini par faire, c'est ci-dessous; à ma façon utilise également un message de la pompe à garder le thread principal disponible pour d'autres choses que de regarder standard, mais cette approche pourrait être utilisée sans message pompes trop.

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();
    }
}

Les mises en garde de cette approche:

  1. le réel de l'enfant .exe qui est lancé doit être une application de console de sorte qu'il reste attaché à stdin//err. Comme dans l'exemple ci-dessus, je m'adapte facilement à mon application existante qui a utilisé une pompe de message (mais à ne pas montrer une interface graphique (GUI) par la simple création d'un petit projet de console qui fait référence à l'existant du projet, l'instanciation de mon contexte de l'application et en appelant Application.Run() à l'intérieur de l' Main méthode de la console .exe.

  2. Techniquement, cela ne les signaux de l'enfant lorsque le parent quitte, donc il fonctionnera si le processus parent est sorti normalement ou s'est écrasé, mais sa reste à l'enfant, les processus à effectuer son propre arrêt. Cela peut ou peut ne pas être ce que vous voulez...

13voto

Marco Regueira Points 182

Il y a une autre méthode, simple et efficace, pour terminer le processus sur la fin du programme. Vous pouvez mettre en œuvre et attacher un débogueur de la mère; lorsque le processus père se termine, le processus enfant sera tué par le système d'exploitation. Il peut aller dans les deux façons d'attacher un débogueur pour le parent de l'enfant (notez que vous ne pouvez attacher un débogueur à un moment). Vous trouverez plus d'infos sur le sujet ici.

Ici vous avez une classe utilitaire qui lance un nouveau processus et attache un débogueur. Il a été adapté à partir de ce poste par Roger Knapp. La seule condition est que les deux processus doivent partager le même nombre de bits. Vous ne pouvez pas déboguer un processus 32 bits de 64 bits processus ou vice versa.

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?
        }
    }
}

Utilisation:

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

12voto

Giorgi Points 15760

Une façon est de passer le PID du processus parent à l’enfant. L’enfant interroge périodiquement si le processus avec le pid spécifié existe ou non. Si ce n’est pas le cas, il sera tout simplement arrêter.

Vous pouvez également utiliser Process.WaitForExit méthode dans la méthode enfant pour être averti lorsque le processus parent se termine mais il pourrait ne pas fonctionner en cas de gestionnaire des tâches.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X