Bunuh proses anak ketika proses orang tua terbunuh

156

Saya membuat proses baru menggunakan System.Diagnostics.Processkelas dari aplikasi saya.

Saya ingin proses ini dimatikan ketika / jika aplikasi saya macet. Tetapi jika saya mematikan aplikasi saya dari Task Manager, proses anak tidak terbunuh.

Apakah ada cara untuk membuat proses anak tergantung pada proses orang tua?

SiberianGuy
sumber

Jawaban:

176

Dari forum ini , berikan penghargaan kepada 'Josh'.

Application.Quit()dan Process.Kill()merupakan solusi yang mungkin, tetapi telah terbukti tidak dapat diandalkan. Ketika aplikasi utama Anda mati, Anda masih memiliki proses anak berjalan. Apa yang kita inginkan adalah agar proses anak mati segera setelah proses utama mati.

Solusinya adalah dengan menggunakan "objek pekerjaan" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .

Idenya adalah untuk membuat "objek pekerjaan" untuk aplikasi utama Anda, dan mendaftarkan proses anak Anda dengan objek pekerjaan. Jika proses utama mati, OS akan mengurus penghentian proses anak.

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

}

Melihat konstruktor ...

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

Kuncinya di sini adalah mengatur objek pekerjaan dengan benar. Dalam konstruktor saya mengatur "batas" ke 0x2000, yang merupakan nilai numerik untuk JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN mendefinisikan bendera ini sebagai:

Menyebabkan semua proses yang terkait dengan pekerjaan berakhir saat pegangan terakhir ke pekerjaan ditutup.

Setelah kelas ini diatur ... Anda hanya perlu mendaftarkan setiap proses anak dengan pekerjaan. Sebagai contoh:

[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);
Matt Howells
sumber
4
Saya akan menambahkan tautan ke CloseHandle
Austin Salonen
6
Sayangnya, saya tidak dapat menjalankan ini dalam mode 64bit. Di sini saya memposting contoh yang berfungsi, yang didasarkan pada ini.
Alexander Yezutov
2
@ Mat Howells - Dari mana Win32.CloseHandleasalnya? Apakah ini diimpor dari kernel32.dll? Ada tanda tangan yang cocok di sana, tetapi Anda tidak mengimpornya secara eksplisit seperti fungsi API lainnya.
Nama Layar Esoterik
6
Untuk aplikasi mode 64 bit -> stackoverflow.com/a/5976162 Untuk masalah Vista / Win7 -> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/…
hB0
3
Oke, MSDN mengatakan: Bendera JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE membutuhkan penggunaan struktur JOBOBJECT_EXTENDED_LIMIT_INFORMATION.
SerG
54

Jawaban ini dimulai dengan jawaban luar biasa @Matt Howells plus lainnya (lihat tautan dalam kode di bawah). Perbaikan:

  • Mendukung 32-bit dan 64-bit.
  • Memperbaiki beberapa masalah dalam jawaban @Matt Howells ':
    1. Kebocoran memori kecil extendedInfoPtr
    2. Kesalahan kompilasi 'Win32', dan
    3. Sebuah pengecualian stack-unbalanced yang saya terima CreateJobObject(menggunakan Windows 10, Visual Studio 2015, 32-bit).
  • Memberi Nama Pekerjaan, jadi jika Anda menggunakan SysInternals, misalnya, Anda dapat dengan mudah menemukannya.
  • Memiliki API yang agak sederhana dan kode yang lebih sedikit.

Berikut cara menggunakan kode ini:

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

Untuk mendukung Windows 7 diperlukan:

Dalam kasus saya, saya tidak perlu mendukung Windows 7, jadi saya memiliki pemeriksaan sederhana di bagian atas konstruktor statis di bawah ini.

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

Saya hati-hati menguji versi 32-bit dan 64-bit dari struct dengan secara terprogram membandingkan versi yang dikelola dan asli satu sama lain (ukuran keseluruhan serta offset untuk masing-masing anggota).

Saya telah menguji kode ini pada Windows 7, 8, dan 10.

Ron
sumber
bagaimana dengan penutupan pekerjaan?
Frank Q.
@ FrankQ. Sangat penting untuk membiarkan Windows menutup s_jobHandle untuk kita ketika proses kita berakhir, karena proses kita mungkin berakhir secara tak terduga (seperti dengan menabrak atau jika pengguna menggunakan Task Manager). Lihat komentar saya di s_jobHandle's.
Ron
Ini bekerja untuk saya. Bolehkah saya bertanya apa pendapat Anda tentang penggunaan bendera JOB_OBJECT_LIMIT_BREAKAWAY_OK? docs.microsoft.com/en-us/windows/desktop/api/winnt/…
Yiping
1
@yiping Kedengarannya seperti CREATE_BREAKAWAY_FROM_JOB memungkinkan proses anak Anda menelurkan proses yang dapat bertahan lebih lama dari proses asli Anda. Itu persyaratan yang berbeda dari yang diminta OP. Sebaliknya, jika Anda dapat membuat proses asli menelurkan proses yang berumur panjang (dan tidak menggunakan ChildProcessTracker) itu akan lebih sederhana.
Ron
@Ron Bisakah Anda menambahkan kelebihan, yang hanya menerima pegangan proses dan bukan seluruh proses?
Jannik
47

Posting ini dimaksudkan sebagai ekstensi untuk jawaban @Matt Howells, khususnya bagi mereka yang mengalami masalah dalam menggunakan Objek Pekerjaan di bawah Vista atau Win7 , terutama jika Anda mendapatkan kesalahan akses ditolak ('5') saat memanggil AssignProcessToJobObject.

tl; dr

Untuk memastikan kompatibilitas dengan Vista dan Win7, tambahkan manifes berikut ke proses induk .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>

Perhatikan bahwa ketika Anda menambahkan manifes baru di Visual Studio 2012 itu akan berisi potongan di atas sehingga Anda tidak perlu menyalinnya dari dengar. Ini juga akan mencakup simpul untuk Windows 8.

penjelasan lengkap

Asosiasi pekerjaan Anda akan gagal dengan kesalahan akses ditolak jika proses yang Anda mulai sudah dikaitkan dengan pekerjaan lain. Masukkan Asisten Kompatibilitas Program, yang, dimulai pada Windows Vista, akan menetapkan semua jenis proses untuk pekerjaannya sendiri.

Di Vista Anda dapat menandai aplikasi Anda untuk dikecualikan dari PCA hanya dengan menyertakan manifes aplikasi. Visual Studio tampaknya melakukan ini untuk aplikasi .NET secara otomatis, jadi Anda baik-baik saja di sana.

Manifes sederhana tidak lagi memotongnya di Win7. [1] Di sana, Anda harus secara spesifik menentukan bahwa Anda kompatibel dengan Win7 dengan tag di manifes Anda. [2]

Ini membuat saya khawatir tentang Windows 8. Apakah saya harus mengubah manifes saya sekali lagi? Rupanya ada celah di awan, karena Windows 8 sekarang memungkinkan proses menjadi milik beberapa pekerjaan. [3] Jadi saya belum mengujinya, tetapi saya membayangkan bahwa kegilaan ini akan berakhir sekarang jika Anda hanya menyertakan manifes dengan informasi OS yang didukung.

Kiat 1 : Jika Anda mengembangkan aplikasi .NET dengan Visual Studio, seperti saya sebelumnya, di sini [4] ada beberapa petunjuk bagus tentang cara menyesuaikan manifes aplikasi Anda.

Tips 2 : Berhati-hatilah dengan meluncurkan aplikasi Anda dari Visual Studio. Saya menemukan bahwa, setelah menambahkan manifes yang sesuai, saya masih memiliki masalah dengan PCA ketika meluncurkan dari Visual Studio, bahkan jika saya menggunakan Start tanpa Debugging. Namun, meluncurkan aplikasi saya dari Explorer berhasil. Setelah secara manual menambahkan devenv untuk pengecualian dari PCA menggunakan registri, memulai aplikasi yang menggunakan Object Ayub dari VS mulai bekerja juga. [5]

Tip 3 : Jika Anda pernah ingin tahu apakah PCA adalah masalah Anda, coba luncurkan aplikasi Anda dari baris perintah, atau salin program ke drive jaringan dan jalankan dari sana. PCA secara otomatis dinonaktifkan dalam konteks tersebut.

[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-karena-kita-mengubah-aturan-pada-Anda.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 : "Suatu proses dapat dikaitkan dengan lebih dari satu pekerjaan di Windows 8"

[4] Bagaimana saya bisa menanamkan manifes aplikasi ke dalam aplikasi menggunakan VS2008?

[5] Bagaimana cara menghentikan debugger Visual Studio memulai proses saya di objek pekerjaan?

adam smith
sumber
2
Ini adalah tambahan yang bagus untuk topik ini, terima kasih! Saya memanfaatkan setiap aspek dari jawaban ini, termasuk tautannya.
Johnny Kauffman
16

Berikut adalah alternatif yang dapat bekerja untuk beberapa saat Anda memiliki kendali atas kode yang dijalankan oleh proses anak. Manfaat dari pendekatan ini adalah tidak memerlukan panggilan Windows asli.

Ide dasarnya adalah untuk mengarahkan input standar anak ke aliran yang ujungnya terhubung ke induk, dan menggunakan aliran itu untuk mendeteksi ketika induk telah pergi. Saat Anda menggunakan System.Diagnostics.Processuntuk memulai anak, mudah untuk memastikan input standarnya dialihkan:

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

Dan kemudian, pada proses anak, manfaatkan fakta bahwa Reads dari input stream standar akan selalu kembali dengan setidaknya 1 byte hingga stream ditutup, ketika mereka akan mulai mengembalikan 0 byte. Garis besar cara saya akhirnya melakukan ini adalah di bawah ini; cara saya juga menggunakan pompa pesan untuk menjaga utas utama tersedia untuk hal-hal selain menonton standar, tetapi pendekatan umum ini dapat digunakan tanpa pompa pesan juga.

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

Peringatan untuk pendekatan ini:

  1. anak sebenarnya .exe yang diluncurkan harus berupa aplikasi konsol sehingga tetap melekat pada stdin / out / err. Seperti dalam contoh di atas, saya dengan mudah mengadaptasi aplikasi saya yang sudah ada yang menggunakan pompa pesan (tetapi tidak menunjukkan GUI) dengan hanya membuat proyek konsol kecil yang mereferensikan proyek yang ada, membuat contoh konteks aplikasi saya dan memanggil Application.Run()ke dalam Mainmetode konsol .exe.

  2. Secara teknis, ini hanya menandakan proses anak ketika orang tua keluar, jadi itu akan bekerja apakah proses orang tua keluar secara normal atau crash, tetapi masih tergantung pada proses anak untuk melakukan shutdown sendiri. Ini mungkin atau mungkin bukan yang Anda inginkan ...

mbaynton
sumber
11

Salah satu caranya adalah dengan memberikan PID proses induk ke anak. Anak itu akan secara berkala melakukan polling jika proses dengan pid yang ditentukan ada atau tidak. Jika tidak, itu hanya akan berhenti.

Anda juga dapat menggunakan metode Process.WaitForExit dalam metode anak untuk diberitahu ketika proses induk berakhir tetapi mungkin tidak berfungsi jika Task Manager.

Giorgi
sumber
Bagaimana saya bisa meneruskan PID induk ke anak? Apakah ada solusi sistem? Saya tidak dapat mengubah biner proses anak.
SiberianGuy
1
Nah, jika Anda tidak dapat mengubah proses anak Anda tidak dapat menggunakan solusi saya bahkan jika Anda meneruskan PID untuk itu.
Giorgi
@Idsa Anda dapat melewatinya melalui baris perintah:Process.Start(string fileName, string arguments)
Distortum
2
Daripada polling, Anda bisa menghubungkan ke acara Keluar di kelas proses.
RichardOD
Sudah mencoba tetapi proses induk tidak selalu keluar jika memiliki anak yang masih hidup (setidaknya dalam kasus saya Cobol-> NET). Mudah untuk memeriksa menonton hierarki proses di Sysinternals ProcessExplorer.
Ivan Ferrer Villa
8

Ada metode lain yang relevan, mudah dan efektif, untuk menyelesaikan proses anak pada penghentian program. Anda dapat menerapkan dan melampirkan debugger ke mereka dari induknya; ketika proses induk berakhir, proses anak akan dibunuh oleh OS. Bisa dua cara melampirkan debugger ke orang tua dari anak (perhatikan bahwa Anda hanya dapat melampirkan satu debugger pada suatu waktu). Anda dapat menemukan info lebih lanjut tentang subjek di sini .

Di sini Anda memiliki kelas utilitas yang meluncurkan proses baru dan melampirkan debugger ke sana. Itu telah diadaptasi dari pos ini oleh Roger Knapp. Satu-satunya persyaratan adalah kedua proses perlu berbagi bitness yang sama. Anda tidak dapat men-debug proses 32bit dari proses 64bit atau sebaliknya.

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

Pemakaian:

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

Saya sedang mencari solusi untuk masalah ini yang tidak memerlukan kode yang tidak dikelola. Saya juga tidak dapat menggunakan redirection input / output standar karena itu adalah aplikasi Windows Forms.

Solusi saya adalah membuat pipa bernama dalam proses induk dan kemudian menghubungkan proses anak ke pipa yang sama. Jika proses induk keluar maka pipa menjadi rusak dan anak dapat mendeteksi ini.

Di bawah ini adalah contoh menggunakan dua aplikasi konsol:

Induk

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

Anak

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);
}
Alsty
sumber
3

Gunakan pengendali acara untuk membuat kait pada beberapa skenario keluar:

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(); };
Justin Harris
sumber
Sangat sederhana namun sangat efektif.
uncommon_name
2

Hanya versi 2018 saya. Gunakan selain metode Main () Anda.

    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);
    }
Lenor
sumber
4
Saya ragu ini akan dieksekusi ketika proses induk dibunuh .
springy76
1

Saya melihat dua opsi:

  1. Jika Anda tahu persis proses anak apa yang bisa dimulai dan Anda yakin itu hanya dimulai dari proses utama Anda, maka Anda bisa mempertimbangkan untuk mencarinya dengan nama dan membunuh mereka.
  2. Iterasi semua proses dan matikan setiap proses yang memiliki proses Anda sebagai orang tua (saya kira Anda harus membunuh proses anak terlebih dahulu). Di sini dijelaskan bagaimana Anda bisa mendapatkan id proses induk.
Stefan Egli
sumber
1

Saya telah membuat perpustakaan manajemen proses anak di mana proses induk dan proses anak dipantau karena pipa WCF dua arah. Jika proses anak berakhir atau proses induk berakhir satu sama lain diberitahu. Ada juga bantuan debugger yang tersedia yang secara otomatis melampirkan debugger VS ke proses anak mulai

Situs proyek:

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

Paket NuGet:

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

Thomas Maierhofer
sumber
0

panggilan pekerjaan. Tambah Proses lebih baik untuk dilakukan setelah memulai proses:

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

Saat memanggil AddProcess sebelum diakhiri, proses anak tidak terbunuh. (Windows 7 SP1)

private void KillProcess(Process proc)
{
    var job = new Job();
    job.AddProcess(proc.Handle);
    job.Close();
}
Alexey
sumber
Memanggil pekerjaan. Menambah Proses setelah memulai proses akan mematikan tujuan menggunakan objek ini.
SerG
0

Namun tambahan lain untuk kekayaan berlimpah solusi yang diajukan sejauh ini ....

Masalah dengan banyak dari mereka adalah bahwa mereka bergantung pada proses orang tua dan anak untuk ditutup secara teratur, yang tidak selalu benar ketika perkembangan sedang berlangsung. Saya menemukan bahwa proses anak saya sering menjadi yatim piatu setiap kali saya menghentikan proses induk di debugger, yang mengharuskan saya untuk membunuh proses yatim piatu dengan Task Manager untuk membangun kembali solusi saya.

Solusinya: Masukkan ID proses induk di pada baris perintah (atau bahkan kurang invasif, dalam variabel lingkungan) dari proses anak.

Dalam proses induk, ID proses tersedia sebagai:

  Process.CurrentProcess.Id;

Dalam proses anak:

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

Saya tidak yakin apakah ini praktik yang dapat diterima dalam kode produksi. Di satu sisi, ini seharusnya tidak pernah terjadi. Tetapi di sisi lain, itu mungkin berarti perbedaan antara memulai kembali suatu proses, dan me-reboot server produksi. Dan apa yang seharusnya tidak pernah terjadi sering terjadi.

Dan itu pasti berguna saat men-debug masalah shutdown tertib.

Robin Davies
sumber