Giết quá trình con khi tiến trình cha mẹ bị giết


156

Tôi đang tạo các quy trình mới bằng cách sử dụng System.Diagnostics.Processlớp từ ứng dụng của mình.

Tôi muốn quá trình này bị hủy khi / nếu ứng dụng của tôi bị hỏng. Nhưng nếu tôi giết ứng dụng của mình từ Trình quản lý tác vụ, các tiến trình con sẽ không bị giết.

Có cách nào để làm cho các tiến trình con phụ thuộc vào quá trình cha mẹ không?

Câu trả lời:


176

Từ diễn đàn này , tín dụng cho 'Josh'.

Application.Quit()Process.Kill()là những giải pháp khả thi, nhưng đã được chứng minh là không đáng tin cậy. Khi ứng dụng chính của bạn chết, bạn vẫn còn các tiến trình con đang chạy. Những gì chúng tôi thực sự muốn là cho các quá trình con chết ngay khi quá trình chính chết.

Giải pháp là sử dụng "đối tượng công việc" http://msdn.microsoft.com/en-us/l Library / ms682409 (VS85) .aspx .

Ý tưởng là tạo ra một "đối tượng công việc" cho ứng dụng chính của bạn và đăng ký các quy trình con của bạn với đối tượng công việc. Nếu quy trình chính bị chết, HĐH sẽ đảm nhiệm việc chấm dứt các quy trình con.

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

}

Nhìn vào nhà xây dựng ...

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

Chìa khóa ở đây là thiết lập đúng đối tượng công việc. Trong hàm tạo tôi đang đặt "giới hạn" thành 0x2000, là giá trị số cho JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.

MSDN định nghĩa cờ này là:

Làm cho tất cả các quy trình liên quan đến công việc chấm dứt khi xử lý cuối cùng cho công việc được đóng lại.

Khi lớp này được thiết lập ... bạn chỉ cần đăng ký từng tiến trình con với công việc. Ví dụ:

[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
Tôi sẽ thêm một liên kết đến CloseHandle
Austin Salonen

6
Thật không may, tôi không thể chạy nó ở chế độ 64 bit. Ở đây tôi đã đăng một ví dụ làm việc, dựa trên điều này.
Alexander Yezutov

2
@Matt Howells - Nguồn Win32.CloseHandlegốc từ đâu? Đây có phải là nhập từ kernel32.dll? Có một chữ ký phù hợp ở đó, nhưng bạn không nhập nó rõ ràng như các hàm API khác.
Tên màn hình bí truyền

6
Đối với các ứng dụng chế độ 64 bit -> stackoverflow.com/a/5976162 Đối với sự cố Vista / Win7 -> social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/iêu
hB0

3
Ok, MSDN nói: Cờ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE yêu cầu sử dụng cấu trúc JOBOB DỰ_EXTENDED_LIMIT_INFORMATION.
SerG

54

Câu trả lời này bắt đầu với câu trả lời tuyệt vời của @Matt Howells cộng với những người khác (xem các liên kết trong mã bên dưới). Cải tiến:

  • Hỗ trợ 32 bit và 64 bit.
  • Khắc phục một số vấn đề trong câu trả lời của @Matt Howells:
    1. Rò rỉ bộ nhớ nhỏ của extendedInfoPtr
    2. Lỗi biên dịch 'Win32' và
    3. Một ngoại lệ không cân bằng ngăn xếp mà tôi nhận được trong cuộc gọi đến CreateJobObject(sử dụng Windows 10, Visual Studio 2015, 32-bit).
  • Đặt tên cho công việc, vì vậy nếu bạn sử dụng SysIternals chẳng hạn, bạn có thể dễ dàng tìm thấy nó.
  • Có API đơn giản hơn và ít mã hơn.

Đây là cách sử dụng mã này:

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

Để hỗ trợ Windows 7 yêu cầu:

Trong trường hợp của tôi, tôi không cần hỗ trợ Windows 7, vì vậy tôi có một kiểm tra đơn giản ở đầu hàm tạo tĩnh bên dưới.

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

Tôi đã kiểm tra cẩn thận cả hai phiên bản 32 bit và 64 bit của các cấu trúc bằng cách lập trình so sánh các phiên bản được quản lý và bản địa với nhau (kích thước tổng thể cũng như độ lệch cho từng thành viên).

Tôi đã kiểm tra mã này trên Windows 7, 8 và 10.


Còn việc đóng cửa xử lý công việc thì sao ??
Frank Q.

@FrankQ. Điều quan trọng là để Windows đóng s_jobHandle cho chúng tôi khi quy trình của chúng tôi kết thúc, bởi vì quy trình của chúng tôi có thể chấm dứt bất ngờ (như do sự cố hoặc nếu người dùng sử dụng Trình quản lý tác vụ). Xem nhận xét của tôi về s_jobHandle's.
Ron

Đó là làm việc cho tôi. Tôi có thể hỏi ý kiến ​​của bạn về việc sử dụng cờ JOB_OBJECT_LIMIT_BREAKAWAY_OK không? docs.microsoft.com/en-us/windows/desktop/api/winnt/ triệt
Yiping

1
@yiping Nghe có vẻ như CREATE_BREAKAWAY_FROM_JOB cho phép quá trình con bạn sinh ra một quy trình có thể tồn tại lâu hơn quy trình ban đầu của bạn. Đó là một yêu cầu khác với những gì OP yêu cầu. Thay vào đó, nếu bạn có thể làm cho quy trình ban đầu của mình sinh ra quy trình tồn tại lâu dài đó (và không sử dụng ChildProcessTracker) thì sẽ đơn giản hơn.
Ron

@Ron Bạn có thể thêm một quá tải, chỉ chấp nhận xử lý quy trình chứ không phải toàn bộ quá trình?
Jannik

47

Bài đăng này nhằm mục đích mở rộng cho câu trả lời của @Matt Howells, đặc biệt dành cho những người gặp vấn đề với việc sử dụng Đối tượng công việc trong Vista hoặc Win7 , đặc biệt nếu bạn gặp lỗi từ chối truy cập ('5') khi gọi AssignProcessToJobObject.

tl; dr

Để đảm bảo khả năng tương thích với Vista và Win7, hãy thêm tệp kê khai sau vào quy trình cha mẹ .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>

Lưu ý rằng khi bạn thêm tệp kê khai mới trong Visual Studio 2012, nó sẽ chứa đoạn mã ở trên, do đó bạn không cần phải sao chép nó từ nghe. Nó cũng sẽ bao gồm một nút cho Windows 8.

giải thích đầy đủ

Hiệp hội công việc của bạn sẽ thất bại với lỗi từ chối truy cập nếu quá trình bạn bắt đầu đã được liên kết với một công việc khác. Nhập Trợ lý Tương thích Chương trình, bắt đầu trong Windows Vista, sẽ chỉ định tất cả các loại quy trình cho công việc của chính nó.

Trong Vista, bạn có thể đánh dấu ứng dụng của mình bị loại khỏi PCA bằng cách bao gồm một bảng kê khai ứng dụng. Visual Studio dường như tự động làm điều này cho các ứng dụng .NET, vì vậy bạn vẫn ổn ở đó.

Một bảng kê khai đơn giản không còn cắt nó trong Win7. [1] Ở đó, bạn phải xác định cụ thể rằng bạn tương thích với Win7 với thẻ trong bảng kê khai của bạn. [2]

Điều này khiến tôi lo lắng về Windows 8. Tôi có phải thay đổi bảng kê khai một lần nữa không? Rõ ràng có một sự phá vỡ trong các đám mây, vì Windows 8 bây giờ cho phép một quá trình thuộc về nhiều công việc. [3] Vì vậy, tôi chưa thử nghiệm nó, nhưng tôi tưởng tượng rằng sự điên rồ này sẽ chấm dứt ngay bây giờ nếu bạn chỉ đơn giản bao gồm một bảng kê khai với thông tin được hỗ trợ.

Mẹo 1 : Nếu bạn đang phát triển ứng dụng .NET với Visual Studio, như tôi đã từng, thì đây là một số hướng dẫn hay về cách tùy chỉnh bảng kê khai ứng dụng của bạn.

Mẹo 2 : Hãy cẩn thận với việc khởi chạy ứng dụng của bạn từ Visual Studio. Tôi thấy rằng, sau khi thêm bản kê khai phù hợp, tôi vẫn gặp vấn đề với PCA khi khởi chạy từ Visual Studio, ngay cả khi tôi đã sử dụng Bắt đầu mà không gỡ lỗi. Tuy nhiên, việc khởi chạy ứng dụng của tôi từ Explorer đã hoạt động. Sau khi thêm thủ công devenv để loại trừ khỏi PCA bằng cách sử dụng sổ đăng ký, bắt đầu các ứng dụng sử dụng Đối tượng công việc từ VS cũng bắt đầu hoạt động. [5]

Mẹo 3 : Nếu bạn muốn biết PCA có phải là vấn đề của mình không, hãy thử khởi chạy ứng dụng của bạn từ dòng lệnh hoặc sao chép chương trình vào ổ đĩa mạng và chạy nó từ đó. PCA được tự động vô hiệu hóa trong các bối cảnh đó.

[1] http://bloss.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an -installer-Take-2-vì-chúng tôi đã thay đổi-quy tắc-on-you.aspx

[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compabilities-assistant

[3] http://msdn.microsoft.com/en-us/l Library / windows / desktop / ms681949 (v = vs85) .aspx : "Một quy trình có thể được liên kết với nhiều hơn một công việc trong Windows 8"

[4] Làm cách nào tôi có thể nhúng một bảng kê khai ứng dụng vào một ứng dụng bằng VS2008?

[5] Làm cách nào để ngăn chặn trình gỡ lỗi Visual Studio bắt đầu quá trình của tôi trong một đối tượng công việc?


2
Đây là những bổ sung tuyệt vời cho chủ đề, cảm ơn bạn! Tôi đã sử dụng mọi khía cạnh của câu trả lời này, bao gồm các liên kết.
Johnny Kauffman

16

Đây là một giải pháp thay thế có thể phù hợp với một số người khi bạn có quyền kiểm soát mã mà tiến trình con chạy. Lợi ích của phương pháp này là nó không yêu cầu bất kỳ cuộc gọi Windows gốc nào.

Ý tưởng cơ bản là chuyển hướng đầu vào tiêu chuẩn của trẻ sang một luồng có đầu kia được kết nối với cha mẹ và sử dụng luồng đó để phát hiện khi cha mẹ đã đi xa. Khi bạn sử dụng System.Diagnostics.Processđể bắt đầu đứa trẻ, thật dễ dàng để đảm bảo đầu vào tiêu chuẩn của nó được chuyển hướng:

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

Và sau đó, trên tiến trình con, hãy tận dụng thực tế là Reads từ luồng đầu vào tiêu chuẩn sẽ luôn trả về với ít nhất 1 byte cho đến khi luồng được đóng, khi chúng sẽ bắt đầu trả về 0 byte. Một phác thảo về cách tôi đã kết thúc việc này là dưới đây; Cách của tôi cũng sử dụng một bơm thông báo để giữ luồng chính có sẵn cho những thứ khác ngoài việc xem tiêu chuẩn, nhưng cách tiếp cận chung này có thể được sử dụng mà không cần bơm thông báo.

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

Hãy cẩn thận với phương pháp này:

  1. .exe con thực tế được khởi chạy phải là một ứng dụng giao diện điều khiển để nó vẫn gắn liền với stdin / out / err. Như trong ví dụ trên, tôi dễ dàng điều chỉnh ứng dụng hiện có sử dụng bơm thông báo (nhưng không hiển thị GUI) bằng cách chỉ tạo một dự án bàn điều khiển nhỏ tham chiếu dự án hiện có, khởi tạo bối cảnh ứng dụng của tôi và gọi Application.Run()bên trong Mainphương thức của giao diện điều khiển .exe.

  2. Về mặt kỹ thuật, điều này chỉ báo hiệu quá trình con khi cha mẹ thoát ra, do đó, nó sẽ hoạt động cho dù tiến trình cha mẹ thoát ra bình thường hay bị hỏng, nhưng nó vẫn phụ thuộc vào quá trình con thực hiện tắt máy của chính nó. Điều này có thể hoặc không thể là những gì bạn muốn ...


11

Một cách là để truyền PID của quá trình cha mẹ cho đứa trẻ. Đứa trẻ sẽ thăm dò định kỳ nếu quá trình với pid được chỉ định có tồn tại hay không. Nếu không nó sẽ bỏ.

Bạn cũng có thể sử dụng phương thức Process.WaitForExit trong phương thức con để được thông báo khi quá trình cha kết thúc nhưng nó có thể không hoạt động trong trường hợp Trình quản lý tác vụ.


Làm thế nào tôi có thể truyền PID cha mẹ cho con? Có giải pháp hệ thống nào không? Tôi không thể sửa đổi nhị phân quá trình con.
SiberianGuy

1
Chà, nếu bạn không thể sửa đổi quy trình con, bạn không thể sử dụng giải pháp của tôi ngay cả khi bạn truyền PID cho nó.
Giorgi

@Idsa Bạn có thể vượt qua nó thông qua dòng lệnh:Process.Start(string fileName, string arguments)
Distortum 17/03/2016

2
Thay vì thăm dò ý kiến, bạn có thể nối vào sự kiện Thoát trên lớp quy trình.
RichardOD

Đã thử nó nhưng quá trình cha mẹ không phải lúc nào cũng thoát nếu nó còn con (ít nhất là trong trường hợp của tôi Cobol-> NET). Dễ dàng kiểm tra xem phân cấp quy trình trong Sysiternals ProcessExplorer.
Biệt thự Ivan Ferrer

8

Có một phương pháp khác có liên quan, dễ dàng và hiệu quả, để kết thúc các quá trình con khi kết thúc chương trình. Bạn có thể thực hiện và đính kèm một trình gỡ lỗi cho chúng từ cha mẹ; khi tiến trình cha kết thúc, các tiến trình con sẽ bị HĐH giết chết. Nó có thể đi cả hai cách gắn một trình gỡ lỗi cho cha mẹ từ đứa trẻ (lưu ý rằng bạn chỉ có thể đính kèm một trình gỡ lỗi tại một thời điểm). Bạn có thể tìm thêm thông tin về chủ đề ở đây .

Ở đây bạn có một lớp tiện ích khởi chạy một quy trình mới và đính kèm một trình gỡ lỗi cho nó. Nó đã được chuyển thể từ bài đăng này của Roger Knapp. Yêu cầu duy nhất là cả hai quá trình cần chia sẻ cùng một bitness. Bạn không thể gỡ lỗi một quy trình 32 bit từ quy trình 64 bit hoặc ngược lại.

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

Sử dụng:

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

8

Tôi đang tìm kiếm một giải pháp cho vấn đề này mà không yêu cầu mã không được quản lý. Tôi cũng không thể sử dụng chuyển hướng đầu vào / đầu ra tiêu chuẩn vì đây là một ứng dụng Windows Forms.

Giải pháp của tôi là tạo ra một đường ống có tên trong quy trình cha và sau đó kết nối quy trình con với cùng một đường ống. Nếu quá trình cha mẹ thoát ra thì đường ống bị vỡ và đứa trẻ có thể phát hiện ra điều này.

Dưới đây là một ví dụ sử dụng hai ứng dụng giao diện điều khiển:

Cha mẹ

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

Đứa trẻ

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

Sử dụng các trình xử lý sự kiện để tạo các hook trên một vài tình huống thoát:

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

Vì vậy, đơn giản nhưng rất hiệu quả.
unommon_name

2

Chỉ là phiên bản 2018 của tôi. Sử dụng nó sang một bên phương thức Main () của bạn.

    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
Tôi nghi ngờ điều này được thực thi khi tiến trình cha mẹ bị giết .
springy76

1

Tôi thấy hai lựa chọn:

  1. Nếu bạn biết chính xác quy trình con nào có thể được bắt đầu và bạn chắc chắn chúng chỉ được bắt đầu từ quy trình chính của bạn, thì bạn có thể xem xét đơn giản là tìm kiếm chúng theo tên và giết chúng.
  2. Lặp lại tất cả các quy trình và tiêu diệt mọi quy trình có quy trình của bạn với tư cách là cha mẹ (tôi đoán trước tiên bạn cần phải giết các quy trình con). Dưới đây là giải thích làm thế nào bạn có thể nhận được id quá trình cha.

1

Tôi đã tạo một thư viện quản lý quy trình con trong đó quy trình cha mẹ và quy trình con được theo dõi do đường ống WCF hai chiều. Nếu quá trình con kết thúc hoặc quá trình cha mẹ chấm dứt lẫn nhau được thông báo. Ngoài ra còn có một trình trợ giúp trình gỡ lỗi có sẵn tự động gắn trình gỡ lỗi VS vào tiến trình con đã bắt đầu

Trang web dự án:

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

Gói NuGet:

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


0

gọi job.AddProcess tốt hơn để làm sau khi bắt đầu quá trình:

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

Khi gọi AddProcess trước khi chấm dứt, các tiến trình con không bị giết. (Windows 7 SP1)

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

Gọi job.AddProcess sau khi bắt đầu quá trình sẽ giết mục đích sử dụng đối tượng này.
SerG

0

Một bổ sung khác cho sự phong phú phong phú của các giải pháp được đề xuất cho đến nay ....

Vấn đề với nhiều người trong số họ là họ dựa vào quá trình cha mẹ và con cái để đóng cửa một cách có trật tự, điều này không phải lúc nào cũng đúng khi quá trình phát triển đang diễn ra. Tôi thấy rằng quá trình con của tôi thường bị mồ côi bất cứ khi nào tôi chấm dứt quá trình cha mẹ trong trình gỡ lỗi, điều này đòi hỏi tôi phải giết (các) quá trình mồ côi với Trình quản lý tác vụ để xây dựng lại giải pháp của mình.

Giải pháp: Truyền ID tiến trình cha vào dòng lệnh (hoặc thậm chí ít xâm lấn hơn, trong các biến môi trường) của quy trình con.

Trong quy trình cha, ID quy trình có sẵn dưới dạng:

  Process.CurrentProcess.Id;

Trong quá trình con:

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

Tôi có hai ý nghĩ về việc liệu đây có phải là thông lệ được chấp nhận trong mã sản xuất hay không. Một mặt, điều này không bao giờ nên xảy ra. Nhưng mặt khác, nó có thể có nghĩa là sự khác biệt giữa khởi động lại một quy trình và khởi động lại máy chủ sản xuất. Và những gì không bao giờ nên xảy ra thường làm.

Và nó chắc chắn là hữu ích trong khi gỡ lỗi các vấn đề tắt máy có trật tự.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.