Ebeveyn süreci öldürüldüğünde çocuk sürecini öldür


156

System.Diagnostics.ProcessUygulamamdaki sınıfı kullanarak yeni işlemler oluşturuyorum .

Uygulamam çöktüğünde / bozulduğunda bu işlemlerin öldürülmesini istiyorum. Ancak başvurumu Görev Yöneticisi'nden öldürürsem çocuk süreçleri öldürülmez.

Alt süreçleri üst sürece bağımlı hale getirmenin bir yolu var mı?

Yanıtlar:


176

Gönderen Bu foruma 'Josh' için, kredi.

Application.Quit()ve Process.Kill()olası çözümlerdir, ancak güvenilir olmadığı kanıtlanmıştır. Ana uygulamanız öldüğünde, alt süreçler çalışmaya devam eder. Gerçekten istediğimiz şey, ana süreçler ölür ölmez çocuk süreçlerinin ölmesidir.

Çözüm, "iş nesneleri" kullanmaktır http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .

Fikir, ana uygulamanız için bir "iş nesnesi" oluşturmak ve alt işlemlerinizi iş nesnesine kaydetmektir. Ana süreç ölürse, OS alt süreçleri sonlandırmaya özen gösterir.

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

}

Kurucuya bakarken ...

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

Buradaki anahtar, iş nesnesini doğru şekilde ayarlamaktır. Yapıcıda "limitleri" sayısal değer olan 0x2000 olarak ayarlıyorumJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE .

MSDN bu bayrağı şu şekilde tanımlar:

İşle ilgili son işlem kapatıldığında işle ilişkili tüm işlemlerin sona ermesine neden olur.

Bu sınıf kurulduktan sonra ... her alt işlemi işe kaydetmeniz yeterlidir. Örneğin:

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


6
Ne yazık ki, bunu 64 bit modunda çalıştıramadım. Burada buna dayalı çalışan bir örnek yayınladım.
Alexander Yezutov

2
@Matt Howells - Nereden geliyor Win32.CloseHandle? Bu kernel32.dll'den mi ithal edildi? Burada eşleşen bir imza var, ancak diğer API işlevleri gibi açıkça almıyorsunuz.
Ezoterik Ekran Adı

6
64 bit mod uygulamaları için -> stackoverflow.com/a/5976162 Vista / Win7 sorunu için -> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/…
hB0 23:12

3
Tamam, MSDN diyor: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE bayrağı bir JOBOBJECT_EXTENDED_LIMIT_INFORMATION yapısının kullanılmasını gerektirir.
SerG

54

Bu cevap @Matt Howells'in mükemmel yanıtı ve diğerleriyle başladı (aşağıdaki koddaki bağlantılara bakın). İyileştirmeler:

  • 32 bit ve 64 bit desteği.
  • @Matt Howells'ın cevabındaki bazı sorunları düzeltir:
    1. Küçük bellek sızıntısı extendedInfoPtr
    2. 'Win32' derleme hatası ve
    3. CreateJobObject(Windows 10, Visual Studio 2015, 32 bit kullanarak) çağrısında bulduğum yığın dengesiz bir istisna .
  • İşi adlandırır, örneğin SysInternals kullanıyorsanız, işi kolayca bulabilirsiniz.
  • Biraz daha basit bir API ve daha az koda sahiptir.

Bu kodu nasıl kullanacağınız aşağıda açıklanmıştır:

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

Windows 7'yi desteklemek için şunlar gerekir:

Benim durumumda, Windows 7'yi desteklememe gerek yoktu, bu yüzden aşağıdaki statik yapıcı üst kısmında basit bir kontrol var.

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

Yönetilen ve yerel sürümleri birbirleriyle programlı olarak karşılaştırarak yapıların hem 32 bit hem de 64 bit sürümlerini dikkatlice test ettim (toplam boyut ve her üye için ofsetler).

Bu kodu Windows 7, 8 ve 10'da test ettim.


kapanış iş kolu hakkında ne ??
Frank S.

@FrankQ. İşlemimiz sona erdiğinde Windows'un bizim için s_jobHandle uygulamasını kapatmasına izin vermek önemlidir, çünkü işlemimiz beklenmedik bir şekilde sonlanabilir (çökme veya kullanıcı Görev Yöneticisi'ni kullanıyorsa). S_jobHandle's hakkındaki yorumuma bakın.
Ron

Benim için çalışıyor. JOB_OBJECT_LIMIT_BREAKAWAY_OK bayrağını kullanma hakkındaki düşüncelerinizi sorabilir miyim? docs.microsoft.com/tr-tr/windows/desktop/api/winnt/…
Yiping

1
@yiping CREATE_BREAKAWAY_FROM_JOB, çocuğunuzun sürecinin orijinal sürecinizi geçebilecek bir süreç ortaya koymasına izin verdiği gibi geliyor. Bu OP'nin istediklerinden farklı bir gerekliliktir. Bunun yerine, orijinal işleminizi bu uzun ömürlü işlemi (ve ChildProcessTracker'ı kullanmama) ortaya çıkarabilirseniz, bu daha basit olacaktır.
Ron

@Ron Tüm süreci değil, sadece işlem tutamacını kabul eden bir aşırı yük ekleyebilir misiniz?
Jannik

47

Bu yazı , özellikle de AssignProcessToJobObject'i çağırırken erişim reddedildi hatası ('5') alırsanız, özellikle Vista veya Win7 altında İş Nesneleri kullanma konusunda sorun yaşayanlar için @Matt Howells 'cevabının bir uzantısı olarak tasarlanmıştır .

tl; Dr.

Vista ve Win7 ile uyumluluğu sağlamak için .NET üst işlemine aşağıdaki bildirimi ekleyin:

<?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'de yeni bildirim eklediğinizde, yukarıdaki snippet'i içerdiğini ve böylece duymaktan kopyalamanıza gerek olmadığını unutmayın. Ayrıca Windows 8 için bir düğüm de içerecektir.

tam açıklama

Başlattığınız işlem başka bir işle ilişkilendirilmişse, iş ilişkiniz erişim reddedildi hatasıyla başarısız olur. Windows Vista'dan başlayarak her türlü işlemi kendi işlerine atayacak Program Uyumluluk Yardımcısı'na girin.

Vista'da, bir uygulama bildirimi ekleyerek uygulamanızı PCA'dan hariç tutulacak şekilde işaretleyebilirsiniz. Visual Studio bunu .NET uygulamaları için otomatik olarak yapıyor gibi görünüyor.

Basit bir bildirim artık Win7'de kesmiyor. [1] Burada, bildiriminizde etiketle Win7 ile uyumlu olduğunuzu belirtmeniz gerekir. [2]

Bu, Windows 8 hakkında endişelenmemi sağladı. Manifestimi bir kez daha değiştirmem gerekecek mi? Görünüşe göre, Windows 8 artık bir sürecin birden fazla işe ait olmasına izin verdiği için bulutlarda bir ara var. [3] Bu yüzden henüz test etmedim, ancak desteklenenOS bilgileriyle bir bildirim eklerseniz bu deliliğin sona ereceğini hayal ediyorum.

İpucu 1 : Olduğum gibi Visual Studio ile bir .NET uygulaması geliştiriyorsanız, burada [4] uygulama bildiriminizi nasıl özelleştirebileceğinizle ilgili bazı güzel talimatlar verilmiştir.

İpucu 2 : Uygulamanızı Visual Studio'dan başlatırken dikkatli olun. Hata ayıklama olmadan Başlat'ı kullansam bile, uygun bildirimi ekledikten sonra Visual Studio'dan başlatırken PCA ile ilgili sorunlar yaşadığımı fark ettim. Ancak uygulamamı Explorer'dan başlatmak işe yaradı. Kayıt defterini kullanarak PCA'dan dışlamak için devenv'i el ile ekledikten sonra, İş Nesneleri'ni VS'den kullanan uygulamaları başlatmak da çalışmaya başladı. [5]

3. İpucu : PCA'nın sorun olup olmadığını bilmek istiyorsanız, uygulamanızı komut satırından başlatmayı deneyin veya programı bir ağ sürücüsüne kopyalayın ve oradan çalıştırın. PCA bu bağlamlarda otomatik olarak devre dışı bırakılır.

[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-çünkü-biz-değiştirdi-kurallar-on-you.aspx

[2] http://ayende.com/blog/4360/how-to-opt-out-of-program- uyumluluk- yardımcı

[3] http://msdn.microsoft.com/tr-tr/library/windows/desktop/ms681949(v=vs.85).aspx : "Bir işlem Windows 8'de birden fazla işle ilişkilendirilebilir"

[4] VS2008 kullanarak bir uygulama bildirimini bir uygulamaya nasıl ekleyebilirim?

[5] Visual Studio hata ayıklayıcısını bir iş nesnesinde işlemimi başlatmayı nasıl durdurabilirim?


2
Bunlar konuya harika eklemeler, teşekkürler! Bağlantılar dahil bu cevabın her yönünü kullandım.
Johnny Kauffman

16

Alt işlemin çalıştırdığı kodu denetlediğinizde, bazıları için işe yarayabilecek bir alternatif aşağıdadır. Bu yaklaşımın yararı, herhangi bir yerel Windows çağrısı gerektirmemesidir.

Temel fikir, çocuğun standart girdisini diğer ucu ebeveyne bağlı olan bir akıma yönlendirmek ve ebeveynin ne zaman gittiğini tespit etmek için bu akışı kullanmaktır. System.Diagnostics.ProcessÇocuğu başlatmak için kullandığınızda , standart girişinin yeniden yönlendirilmesini sağlamak kolaydır:

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

Ve sonra, alt süreçte, Readstandart giriş akışından gelenlerin, 0 bit döndürmeye başlayacakları zaman, akış kapatılana kadar her zaman en az 1 baytla döneceği gerçeğinden yararlanın . Bunu yapmamın bir özeti aşağıda; benim yolum aynı zamanda ana iş parçacığı standart izlemek dışında şeyler için kullanılabilir tutmak için bir mesaj pompası kullanır, ancak bu genel yaklaşım da mesaj pompaları olmadan kullanılabilir.

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

Bu yaklaşıma ilişkin uyarılar:

  1. başlatılan gerçek alt .exe bir konsol uygulaması olması gerekir, böylece stdin / out / err bağlı kalır. Yukarıdaki örnekte olduğu gibi, ben kolayca, sadece, mevcut projeyi başvurulan bu küçücük konsol proje oluşturma başvurum bağlamını başlatmasını ve arayarak bir mesaj pompa kullanılabilir (ancak bir GUI göstermedi) Mevcut uygulama adapte Application.Run()Mainyöntemine konsol .exe.

  2. Teknik olarak, bu sadece ebeveyn çıktığı zaman çocuk sürecini işaret eder, bu nedenle ebeveyn sürecinin normal bir şekilde çıkıp çıkmadığı veya çöktüğü çalışır, ancak yine de kendi kapatma işlemini gerçekleştirmek için alt süreçlere bağlıdır. İstediğiniz şey bu olabilir veya olmayabilir ...


11

Bunun bir yolu, ana sürecin PID'sini çocuğa iletmektir. Çocuk, belirtilen pid ile işlem olup olmadığını periyodik olarak yoklar. Değilse sadece bırakacaktır.

Üst işlem sona erdiğinde bildirimde bulunmak için child yönteminde Process.WaitForExit yöntemini de kullanabilirsiniz , ancak Görev Yöneticisi durumunda çalışmayabilir.


Ebeveyn PID'sini çocuğa nasıl iletebilirim? Herhangi bir sistem çözümü var mı? Alt işlem ikili dosyalarını değiştiremiyorum.
SiberianGuy

1
Alt işlemi değiştiremiyorsanız, PID'yi ona iletseniz bile çözümümü kullanamazsınız.
Giorgi

Sen komut satırı üzerinden iletebilirsiniz @Idsa:Process.Start(string fileName, string arguments)
Distortum

2
Anket yerine işlem sınıfındaki Exit olayına bağlanabilirsiniz.
RichardOD

Denedim ama çocuk yaşıyorsa ana süreç her zaman çıkmaz (en azından benim durumumda Cobol-> NET). Sysinternals ProcessExplorer'da işlem hiyerarşisini izlerken kontrolü kolaydır.
Ivan Ferrer Villa

8

Program sonlandırıldığında alt süreçleri bitirmek için kolay ve etkili bir başka yöntem daha vardır. Üst öğeden bunlara bir hata ayıklayıcı uygulayabilir ve ekleyebilirsiniz ; üst süreç sona erdiğinde, alt süreçler İS tarafından öldürülecektir. Çocuktan ebeveyne bir hata ayıklayıcı iliştirmek her iki yöne de gidebilir (bir kerede yalnızca bir hata ayıklayıcı ekleyebileceğinizi unutmayın). Konu hakkında daha fazla bilgiyi burada bulabilirsiniz .

Burada yeni bir işlem başlatan ve buna bir hata ayıklayıcı ekleyen bir yardımcı sınıf var. Roger Knapp tarafından bu yazıdan uyarlanmıştır . Tek gereklilik, her iki işlemin de aynı bitliği paylaşması gerektiğidir. 32bit işleminde 64bit işleminde hata ayıklama yapamazsınız veya bunun tersi de geçerlidir.

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

Kullanımı:

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

8

Yönetilmeyen kod gerektirmeyen bu soruna bir çözüm arıyordum. Windows Forms uygulaması olduğu için standart giriş / çıkış yeniden yönlendirmesini de kullanamadım.

Benim çözümüm, üst süreçte adlandırılmış bir boru oluşturmak ve daha sonra alt işlemi aynı boruya bağlamaktı. Ana işlemden çıkılırsa boru kırılır ve çocuk bunu algılayabilir.

Aşağıda iki konsol uygulaması kullanan bir örnek verilmiştir:

ebeveyn

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

Çocuk

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

Birkaç çıkış senaryosunda kanca yapmak için olay işleyicileri kullanın :

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

Çok basit ama çok etkili.
uncommon_name

2

Sadece 2018 versiyonum. Main () yönteminizi bir kenara koyun.

    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
Bunun ebeveyn süreci öldürüldüğünde gerçekleştiğinden şüpheliyim .
springy76

1

İki seçenek görüyorum:

  1. Hangi alt işlemin başlatılabileceğini tam olarak biliyorsanız ve bunların yalnızca ana işleminizden başlatıldığından eminseniz, bunları yalnızca adıyla aramayı ve öldürmeyi düşünebilirsiniz.
  2. Tüm süreçler boyunca yineleyin ve bir ebeveyn olarak süreciniz olan her süreci öldürün (sanırım önce çocuk süreçlerini öldürmeniz gerekir). Üst süreç kimliğini nasıl alabileceğiniz aşağıda açıklanmıştır.

1

Üst işlem ve alt işlemin çift yönlü bir WCF borusu nedeniyle izlendiği bir alt işlem yönetimi kitaplığı yaptım. Alt işlem sonlanırsa veya üst işlem sonlandırılırsa bildirilir. VS hata ayıklayıcısını başlatılan alt sürece otomatik olarak bağlayan bir hata ayıklayıcı yardımcısı da vardır

Proje sitesi:

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

NuGet Paketleri:

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


0

Işlem çağırın.AddProcess sürecin başlamasından sonra daha iyi:

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

Sonlandırmadan önce AddProcess çağrılırken, alt işlemler öldürülmez. (Windows 7 SP1)

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

İş çağrılıyor.AddProcess, işlem başladıktan sonra bu nesneyi kullanma amacını öldürür.
SerG

0

Şimdiye kadar önerilen çözümlerin zenginliğine bir başka katkı ....

Birçoğunun problemi, düzenli bir şekilde kapanmak için ebeveyn ve çocuk sürecine güveniyor olmalarıdır, bu da gelişme devam ederken her zaman doğru değildir. Hata ayıklayıcıdaki ana işlemi sonlandırdığımda çocuk sürecimin çoğu zaman öksüz kaldığını gördüm, bu da çözümümü yeniden oluşturmak için Görev Yöneticisi ile yetim (ler) i öldürmemi gerektiriyordu.

Çözüm: Üst işlem kimliğini, alt işlemin komut satırına (veya ortam değişkenlerinde daha az invaziv) geçirin.

Üst işlemde, işlem kimliği şu şekilde kullanılabilir:

Process.CurrentProcess.Id;

Alt süreçte:

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

Bunun üretim kodunda kabul edilebilir bir uygulama olup olmadığına dair iki fikrim var. Bir yandan, bu asla olmamalı. Ancak diğer yandan, bir süreci yeniden başlatmak ile bir üretim sunucusunu yeniden başlatmak arasındaki fark anlamına gelebilir. Ve asla olmamalı ne sıklıkta yapar.

Ve düzenli kapatma sorunlarının hatalarını ayıklarken yararlı olur.


0

Benim için çalışan çözüm:

İşlem ekleme etiketi oluştururken process.EnableRaisingEvents = true;:

csc = new Process();
csc.StartInfo.UseShellExecute = false;
csc.StartInfo.CreateNoWindow = true;
csc.StartInfo.FileName = Path.Combine(HLib.path_dataFolder, "csc.exe");
csc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
csc.StartInfo.ErrorDialog = false;
csc.StartInfo.RedirectStandardInput = true;
csc.StartInfo.RedirectStandardOutput = true;
csc.EnableRaisingEvents = true;
csc.Start();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.