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);
Win32.CloseHandle
asalnya? Apakah ini diimpor dari kernel32.dll? Ada tanda tangan yang cocok di sana, tetapi Anda tidak mengimpornya secara eksplisit seperti fungsi API lainnya.Jawaban ini dimulai dengan jawaban luar biasa @Matt Howells plus lainnya (lihat tautan dalam kode di bawah). Perbaikan:
extendedInfoPtr
CreateJobObject
(menggunakan Windows 10, Visual Studio 2015, 32-bit).Berikut cara menggunakan kode ini:
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.
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.
sumber
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:
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?
sumber
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.Process
untuk memulai anak, mudah untuk memastikan input standarnya dialihkan:Dan kemudian, pada proses anak, manfaatkan fakta bahwa
Read
s 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.Peringatan untuk pendekatan ini:
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 dalamMain
metode konsol .exe.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 ...
sumber
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.
sumber
Process.Start(string fileName, string arguments)
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.
Pemakaian:
sumber
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
Anak
sumber
Gunakan pengendali acara untuk membuat kait pada beberapa skenario keluar:
sumber
Hanya versi 2018 saya. Gunakan selain metode Main () Anda.
sumber
Saya melihat dua opsi:
sumber
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/
sumber
panggilan pekerjaan. Tambah Proses lebih baik untuk dilakukan setelah memulai proses:
Saat memanggil AddProcess sebelum diakhiri, proses anak tidak terbunuh. (Windows 7 SP1)
sumber
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:
Dalam proses anak:
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.
sumber