Убийте дочірній процес, коли батьківський процес убитий


156

Я створюю нові процеси за допомогою System.Diagnostics.Processкласу зі своєї програми.

Я хочу, щоб ці процеси були знищені, коли / якщо мій додаток вийшов з ладу. Але якщо я вбиваю свою програму від диспетчера завдань, дочірні процеси не вбиваються.

Чи є спосіб зробити так, щоб дочірні процеси залежали від батьківського процесу?

Відповіді:


176

З цього форуму , кредит на "Джош".

Application.Quit()і Process.Kill()є можливими рішеннями, але виявилися ненадійними. Коли ваша основна програма помирає, ви все ще залишаєтеся запущеними дочірніми процесами. Ми дійсно хочемо, щоб дитячі процеси вмирали, як тільки помирає основний процес.

Рішення полягає у використанні "об'єктів завдання" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .

Ідея полягає в тому, щоб створити "об'єкт завдання" для вашої основної програми та зареєструвати ваші дочірні процеси об'єктом завдання. Якщо основний процес загине, ОС подбає про припинення дочірніх процесів.

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

}

Дивлячись на конструктора ...

JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;

Ключовим тут є правильне налаштування об'єкта завдання. У конструкторі я встановлюю "межі" на 0x2000, що є числовим значенням для JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN визначає цей прапор як:

Причиняє завершення всіх процесів, пов’язаних із завданням, коли остання ручка до завдання закрита.

Після того, як цей клас буде налаштований ... ви просто повинні зареєструвати кожен дочірній процес із завданням. Наприклад:

[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);

4
Я б додав посилання на CloseHandle
Остін Салонен

6
На жаль, мені не вдалося запустити це в 64-бітовому режимі. Тут я розмістив робочий приклад, який ґрунтується на цьому.
Олександр Єзутов

2
@Matt Howells - звідки Win32.CloseHandleпоходить? Це імпортовано з kernel32.dll? Там є відповідна підпис, але ви не імпортуєте її явно, як і інші функції API.
Езотеричне ім'я екрана

6
Для програм у 64-бітному режимі -> stackoverflow.com/a/5976162 Для випуску Vista / Win7 -> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/…
hB0

3
Гаразд, MSDN говорить: прапор JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE вимагає використання структури JOBOBJECT_EXTENDED_LIMIT_INFORMATION.
СерГ

54

Ця відповідь почалася з відмінної відповіді @Matt Howells плюс інших (див. Посилання в коді нижче). Покращення:

  • Підтримує 32-розрядні та 64-бітні.
  • Виправляє деякі проблеми у відповіді @Matt Howells:
    1. Невеликий витік пам'яті extendedInfoPtr
    2. Помилка компіляції 'Win32' та
    3. Незбалансований стек винятків я отримав під час виклику CreateJobObject(використовуючи Windows 10, Visual Studio 2015, 32-розрядний).
  • Назвіть роботу, тому, якщо, наприклад, використовуєте SysInternals, ви можете легко знайти її.
  • Має дещо простіший API та менше коду.

Ось як використовувати цей код:

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

Для підтримки Windows 7 потрібно:

У моєму випадку мені не потрібно було підтримувати Windows 7, тому я маю просту перевірку вгорі статичного конструктора внизу.

/// <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;
}

Я ретельно протестував як 32-бітну, так і 64-бітну версії конструкцій, програмно порівнюючи керовану та нативну версії між собою (загальний розмір, а також компенсації для кожного члена).

Я перевірив цей код на Windows 7, 8 та 10.


як щодо закриття ручки роботи ??
Френк Q.

@FrankQ. Важливо дозволити Windows закрити s_jobHandle для нас, коли наш процес припиняється, тому що наш процес може несподівано закінчитися (наприклад, через збої або якщо користувач використовує диспетчер завдань). Дивіться мій коментар щодо s_jobHandle.
Рон

Це працює для мене. Чи можу я запитати, яка ваша думка щодо використання прапора JOB_OBJECT_LIMIT_BREAKAWAY_OK? docs.microsoft.com/en-us/windows/desktop/api/winnt/…
Yiping

1
@yiping Це здається, що CREATE_BREAKAWAY_FROM_JOB дозволяє вашому дочірньому процесу нерестувати процес, який може пережити ваш початковий процес. Це інша вимога, ніж те, про що вимагала ОП. Натомість, якщо ви можете зробити свій оригінальний процес нерестувати цей довговічний процес (а не використовувати ChildProcessTracker), це було б простіше.
Рон

@Ron Чи можете ви додати перевантаження, яке приймає лише ручку процесу, а не весь процес?
Jannik

47

Ця публікація призначена як доповнення до відповіді @Matt Howells, спеціально для тих, хто стикається з проблемами використання об'єктів роботи під Vista або Win7 , особливо якщо ви отримуєте помилку в доступі, заборонену ("5") під час виклику AssignProcessToJobObject.

тл; д-р

Щоб забезпечити сумісність з Vista та Win7, додайте наступний маніфест до батьківського процесу .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>

Зауважте, що коли ви додаєте новий маніфест у Visual Studio 2012, він буде містити вже описаний вище фрагмент, тому вам не потрібно копіювати його з чуття. Він також буде включати вузол для Windows 8.

повне пояснення

Асоціація вашої роботи не зможе отримати помилку з відхиленням у доступі, якщо процес, який ви починаєте, вже пов’язаний з іншою роботою. Увійдіть до Помічника сумісності програм, який, починаючи з Windows Vista, призначить всі види процесів власним завданням.

У Vista ви можете вказати, що ваш додаток буде виключено з PCA, просто включивши маніфест програми. Здається, Visual Studio робить це для додатків .NET автоматично, тож там вам добре.

Простий маніфест більше не скорочує його в Win7. [1] Там ви повинні конкретно вказати, що ви сумісні з Win7 з тегом у вашому маніфесті. [2]

Це змусило мене турбуватися щодо Windows 8. Чи доведеться ще раз змінити свій маніфест? Мабуть, в облаках перерва, оскільки Windows 8 тепер дозволяє процесу належати до кількох завдань. [3] Тому я ще цього не перевіряв, але думаю, що це безумство закінчиться зараз, якщо ви просто включите маніфест із підтримуваною інформацієюOS.

Порада 1 : Якщо ви розробляєте додаток .NET з Visual Studio, як я, тут [4] є кілька приємних інструкцій щодо налаштування маніфесту програми.

Порада 2 : Будьте уважні до запуску програми з Visual Studio. Я виявив, що, додавши відповідний маніфест, у мене все-таки виникли проблеми з PCA при запуску з Visual Studio, навіть якщо я використовував Start без налагодження. Однак запуск моєї програми від Explorer працював. Після вручну додавання devenv для виключення з PCA за допомогою реєстру, також почали працювати програми, які використовували Job Objects від VS. [5]

Порада 3 : Якщо ви коли-небудь хочете дізнатися, чи проблема у вас PCA, спробуйте запустити свою програму з командного рядка або скопіюйте програму на мережевий диск та запустіть її звідти. PCA автоматично відключається в цих контекстах.

[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-jer-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 : "Процес може бути пов'язаний з більш ніж однією роботою в Windows 8"

[4] Як я можу вставити маніфест програми у програму за допомогою VS2008?

[5] Як зупинити налагоджувач Visual Studio, починаючи мій процес в об'єкті роботи?


2
Це чудові доповнення до теми, дякую! Я використав кожен аспект цієї відповіді, включаючи посилання.
Джонні Кауффман

16

Ось альтернатива, яка може працювати для деяких, коли у вас є контроль над кодом, який працює дочірній процес. Перевага такого підходу полягає в тому, що він не вимагає жодних викликів рідної Windows.

Основна ідея полягає в тому, щоб перенаправити стандартний вклад дитини на потік, інший кінець якого підключений до батьківського, і використовувати цей потік для виявлення, коли батько відійшов. Коли ви використовуєте System.Diagnostics.Processдля запуску дитини, легко переконатися, що його стандартний ввід перенаправлено:

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

А потім у дочірньому процесі скористайтеся тим, що Reads зі стандартного вхідного потоку завжди повертається щонайменше з 1 байта, поки потік не закриється, коли вони почнуть повертати 0 байт. Опис того, як я закінчила це робити, є нижче; мій спосіб також використовує насос повідомлень, щоб підтримувати основну нитку для речей, окрім перегляду стандартних, але цей загальний підхід може бути використаний і без насосів повідомлень.

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

Застереження до цього підходу:

  1. власне дочірнє .exe, яке запускається, повинно бути консольним додатком, тому воно залишається приєднаним до stdin / out / err. Як і у наведеному вище прикладі, я легко адаптував існуючу програму, яка використовувала насос для повідомлень (але не показувала графічний інтерфейс), просто створивши крихітний проект консолі, на який посилався на існуючий проект, інстанціюючи мій контекст програми та закликаючи Application.Run()всередину Mainметоду консоль .exe.

  2. Технічно це просто сигналізує про дочірній процес, коли батько виходить, тому він буде працювати, чи нормально вийшов з батьківського процесу або вийшов з ладу, але його все ще залежить від дочірніх процесів для власного відключення. Це може бути, а може і не бути тим, що ви хочете ...


11

Один із способів - передавати дитині PID-процес батьківського процесу. Дитина буде періодично опитуватись, чи існує процес із зазначеним підом чи ні. Якщо ні, то просто вийдуть.

Ви також можете використовувати метод Process.WaitForExit у дочірньому методі, щоб отримувати повідомлення про те, коли батьківський процес закінчується, але він може не працювати у випадку диспетчера завдань.


Як я можу передати батьківський PID дитині? Чи є якесь системне рішення? Я не можу змінювати бінарні файли дочірнього процесу.
SiberianGuy

1
Ну, якщо ви не можете змінити дочірній процес, ви не можете використовувати моє рішення, навіть якщо ви передасте йому PID.
Giorgi

@Idsa Ви можете передати його через командний рядок:Process.Start(string fileName, string arguments)
Distortum

2
Замість опитування ви можете підключитись до події Exit у класі процесу.
RichardOD

Спробував це, але батьківський процес не завжди закінчується, якщо у нього живі діти (принаймні в моєму випадку Cobol-> NET). Легко перевірити перегляд ієрархії процесів у Sysinternals ProcessExplorer.
Вілла Івана Феррера

8

Існує ще один відповідний метод, простий та ефективний, щоб закінчити дочірні процеси по завершенню програми. Ви можете реалізувати та приєднати налагоджувач до них від батьків; коли закінчиться батьківський процес, дочірні процеси будуть вбиті ОС. Це може йти обома способами приєднання налагоджувача до батька від дитини (зауважте, що ви можете приєднувати лише один налагоджувач одночасно). Ви можете знайти більше інформації з цього питання тут .

Тут у вас є клас утиліти, який запускає новий процес і приєднує до нього відладчик. Він був адаптований з цієї посади Роджером Кнаппом. Єдина вимога полягає в тому, що обом процесам потрібно ділити однаковий біт. Ви не можете налагоджувати 32-бітовий процес із 64-бітового або навпаки.

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

Використання:

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

8

Я шукав рішення цієї проблеми, яка не потребувала некерованого коду. Я також не зміг використовувати стандартне перенаправлення вводу / виводу, оскільки це був додаток Windows Forms.

Моє рішення полягало в тому, щоб створити іменовану трубку в батьківському процесі, а потім підключити дочірній процес до тієї ж труби. Якщо вихідний процес закінчується, труба розривається і дитина може це виявити.

Нижче наводиться приклад використання двох консольних програм:

Батьківський

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

Дитина

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

Використовуйте обробники подій, щоб зробити гачки на кількох сценаріях виходу:

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

Просто моя версія 2018 року. Використовуйте його в стороні вашого методу 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);
    }

4
Я сумніваюся, що це виконується, коли батьківський процес вбивається .
springy76

1

Я бачу два варіанти:

  1. Якщо ви точно знаєте, який дочірній процес можна було запустити, і ви впевнені, що він розпочався лише з вашого основного процесу, тоді ви можете подумати про те, щоб просто знайти їх по імені та вбити.
  2. Проводьте всі процеси і вбивайте кожен процес, який має ваш процес як батьків (я думаю, вам потрібно спочатку вбити дочірні процеси). Тут пояснено, як можна отримати ідентифікатор батьківського процесу.

1

Я створив бібліотеку управління дочірніми процесами, де моніторинг батьківського процесу та дочірнього процесу здійснюється через двонаправлену трубу WCF. Якщо або дочірній процес припиняється, або батьківський процес припиняється, один одного повідомляється. Також доступний помічник налагодження, який автоматично приєднує відладчик VS до запущеного дочірнього процесу

Сайт проекту:

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

Пакети NuGet:

https://www.nuget.org/packages/ChildProcess https://www.nuget.org/packages/ChildProcess.VisualStudioDebug/


0

виклик роботи.AddProcess краще робити після початку процесу:

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

При виклику AddProcess перед припиненням, дочірні процеси не вбиваються. (Windows 7 SP1)

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

Виклик завдання.AddProcess після запуску процесу знищить мету використання цього об'єкта.
СерГ

0

Ще одне доповнення до рясного багатства рішень, пропонованих на сьогодні….

Проблема багатьох з них полягає в тому, що вони покладаються на процес батьків і дітей, щоб вимкнутись впорядковано, що не завжди вірно, коли йде розвиток. Я виявив, що мій дочірній процес часто осиротів, коли я припиняв батьківський процес у відладці, що вимагало від мене вбити осиротілих процесів за допомогою диспетчера завдань, щоб відновити своє рішення.

Рішення: Введіть ідентифікатор батьківського процесу в командному рядку (або навіть менш інвазивно, в змінні середовища) дочірнього процесу.

У батьківському процесі ідентифікатор процесу доступний у вигляді:

  Process.CurrentProcess.Id;

У процесі дитини:

Process parentProcess = Process.GetProcessById(parentProcessId);
parentProcess.Exited += (s, e) =>
{
    // clean up what you can.
    this.Dispose();
    // maybe log an error
    ....

    // And terminate with prejudice! 
    //(since something has already gone terribly wrong)
    Process.GetCurrentProcess().Kill();
}

Я замислююся про те, чи прийнятна це практика у виробничому коді. З одного боку, цього ніколи не повинно відбуватися. Але з іншого боку, це може означати різницю між перезапуском процесу та перезавантаженням виробничого сервера. І те, що ніколи не повинно траплятися, часто робить.

І це, звичайно, корисно під час налагодження впорядкованих проблем із відключенням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.