От этого форума , кредит на «Джош».
Application.Quit()
и Process.Kill()
являются возможными решениями, но оказались ненадежными. Когда ваше основное приложение умирает, у вас все еще остаются запущенные дочерние процессы. Мы действительно хотим, чтобы дочерние процессы умирали, как только умирает основной процесс.
Решение состоит в том, чтобы использовать «объекты заданий» http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
Идея состоит в том, чтобы создать «объект задания» для вашего основного приложения и зарегистрировать ваши дочерние процессы с объектом задания. Если основной процесс умирает, ОС позаботится о прекращении дочерних процессов.
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public Int16 LimitFlags;
public UInt32 MinimumWorkingSetSize;
public UInt32 MaximumWorkingSetSize;
public Int16 ActiveProcessLimit;
public Int64 Affinity;
public Int16 PriorityClass;
public Int16 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UInt32 ProcessMemoryLimit;
public UInt32 JobMemoryLimit;
public UInt32 PeakProcessMemoryUsed;
public UInt32 PeakJobMemoryUsed;
}
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(object a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
private IntPtr m_handle;
private bool m_disposed = false;
public Job()
{
m_handle = CreateJobObject(null, null);
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedInfo.BasicLimitInformation = info;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
private void Dispose(bool disposing)
{
if (m_disposed)
return;
if (disposing) {}
Close();
m_disposed = true;
}
public void Close()
{
Win32.CloseHandle(m_handle);
m_handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr handle)
{
return AssignProcessToJobObject(m_handle, handle);
}
}
Глядя на конструктор ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
Ключевым моментом здесь является правильная настройка объекта задания. В конструкторе я устанавливаю «пределы» на 0x2000, что является числовым значением для JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
MSDN определяет этот флаг как:
Вызывает завершение всех процессов, связанных с заданием, когда закрывается последний дескриптор задания.
После настройки этого класса ... вам просто нужно зарегистрировать каждый дочерний процесс в задании. Например:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
Excel.Application app = new Excel.ApplicationClass();
uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
job.AddProcess(Process.GetProcessById((int)pid).Handle);
Win32.CloseHandle
происходит? Это импортируется из kernel32.dll? Там есть соответствующая подпись, но вы не импортируете ее явно, как другие функции API.Этот ответ начался с отличного ответа @Matt Howells плюс другие (см. Ссылки в коде ниже). Улучшения:
extendedInfoPtr
CreateJobObject
(с использованием Windows 10, Visual Studio 2015, 32-разрядная версия).Вот как использовать этот код:
Для поддержки Windows 7 требуется:
В моем случае мне не нужно было поддерживать Windows 7, поэтому у меня есть простая проверка вверху статического конструктора ниже.
Я тщательно протестировал как 32-битные, так и 64-битные версии структур, программно сравнивая управляемые и собственные версии друг с другом (общий размер, а также смещения для каждого элемента).
Я тестировал этот код на Windows 7, 8 и 10.
источник
Этот пост предназначен для расширения ответа @Matt Howells, особенно для тех, кто сталкивается с проблемами при использовании объектов заданий в Vista или Win7 , особенно если вы получаете ошибку отказа в доступе ('5') при вызове AssignProcessToJobObject.
ТЛ; др
Чтобы обеспечить совместимость с Vista и Win7, добавьте следующий манифест в родительский процесс .NET:
Обратите внимание, что при добавлении нового манифеста в Visual Studio 2012 он уже будет содержать приведенный выше фрагмент кода, поэтому вам не нужно копировать его из списка прослушивания. Он также будет включать узел для Windows 8.
полное объяснение
Ваша ассоциация заданий потерпит неудачу с ошибкой доступа, если запускаемый вами процесс уже связан с другим заданием. Введите Ассистент совместимости программ, который, начиная с Windows Vista, будет назначать все виды процессов для своих собственных заданий.
В Vista вы можете пометить ваше приложение как исключенное из PCA, просто включив манифест приложения. Visual Studio, кажется, делает это для приложений .NET автоматически, так что все в порядке.
Простой манифест больше не режет его в Win7. [1] Там вы должны указать, что вы совместимы с Win7 с тегом в манифесте. [2]
Это заставило меня задуматься о Windows 8. Должен ли я изменить свой манифест еще раз? Очевидно, в облаках есть перерыв, поскольку Windows 8 теперь позволяет процессу принадлежать нескольким заданиям. [3] Так что я еще не проверял это, но я думаю, что это безумие закончится, если вы просто включите манифест с информацией о поддерживаемых ОС.
Совет 1 : Если вы разрабатываете приложение .NET с Visual Studio, как я, здесь [4] приведены несколько хороших инструкций по настройке манифеста вашего приложения.
Совет 2 : будьте осторожны при запуске приложения из Visual Studio. Я обнаружил, что после добавления соответствующего манифеста у меня все еще были проблемы с PCA при запуске из Visual Studio, даже если я использовал Пуск без отладки. Однако запуск приложения из Проводника сработал. После добавления devenv вручную для исключения из PCA с использованием реестра, также начали работать приложения, использующие Job Objects из VS. [5]
Совет 3 : Если вы когда-нибудь захотите узнать, является ли PCA вашей проблемой, попробуйте запустить приложение из командной строки или скопировать программу на сетевой диск и запустить его оттуда. PCA автоматически отключается в этих условиях.
[1] http://blogs.msdn.com/b/cjacks/archive/2009/06/18/pca-changes-for-windows-7-how-to-tell-us-you-are-not-an -installer-дубль-2-потому-мы-изменил-на-правила-на-you.aspx
[2] http://ayende.com/blog/4360/how-to-opt-out-of-program-compatibility-assistant
[3] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681949(v=vs.85).aspx : «Процесс может быть связан с несколькими заданиями в Windows 8»
[4] Как я могу встроить манифест приложения в приложение с использованием VS2008?
[5] Как остановить отладчик Visual Studio, запускающий мой процесс в объекте задания?
источник
Вот альтернатива, которая может работать для некоторых, когда у вас есть контроль над кодом, который запускает дочерний процесс. Преимущество этого подхода заключается в том, что он не требует никаких вызовов Windows.
Основная идея состоит в том, чтобы перенаправить стандартный ввод дочернего элемента в поток, другой конец которого подключен к родительскому элементу, и использовать этот поток, чтобы определить, когда родительский элемент исчез. Когда вы используете
System.Diagnostics.Process
дочерний элемент, легко убедиться, что его стандартный ввод перенаправлен:И затем, на дочернем процессе, воспользуйтесь тем фактом, что
Read
s из стандартного входного потока всегда будет возвращаться по крайней мере с 1 байтом, пока поток не будет закрыт, когда они начнут возвращать 0 байтов. Схема того, как я это сделал, приведена ниже; Мой путь также использует рассылку сообщений, чтобы держать основной поток доступным для вещей, отличных от просмотра стандарта, но этот общий подход можно использовать и без рассылок сообщений.Предостережения к этому подходу:
фактический дочерний файл .exe, который запускается, должен быть консольным приложением, поэтому он остается подключенным к stdin / out / err. Как и в приведенном выше примере, я легко адаптировал свое существующее приложение, которое использовало насос сообщений (но не показывал графический интерфейс), просто создав крошечный консольный проект, который ссылался на существующий проект, создавал экземпляр моего контекста приложения и вызывал
Application.Run()
внутриMain
метода консоль .exe.Технически это просто сигнализирует о дочернем процессе при выходе из родительского процесса, поэтому он будет работать независимо от того, завершился ли родительский процесс нормально или произошел сбой, но его дочерние процессы по-прежнему должны самостоятельно завершать работу. Это может или не может быть то, что вы хотите ...
источник
Одним из способов является передача PID родительского процесса ребенку. Ребенок будет периодически опрашивать, существует ли процесс с указанным pid или нет. Если нет, то просто выйдет.
Вы также можете использовать метод Process.WaitForExit в дочернем методе, чтобы получать уведомления о завершении родительского процесса, но он может не работать в случае диспетчера задач.
источник
Process.Start(string fileName, string arguments)
Существует другой соответствующий метод, простой и эффективный, для завершения дочерних процессов после завершения программы. Вы можете реализовать и прикрепить к ним отладчик от родителя; Когда родительский процесс завершается, дочерние процессы будут уничтожены ОС. Прикрепить отладчик к родительскому элементу от дочернего процесса можно двумя способами (обратите внимание, что одновременно можно подключить только один отладчик). Вы можете найти больше информации по этому вопросу здесь .
Здесь у вас есть служебный класс, который запускает новый процесс и присоединяет к нему отладчик. Это было адаптировано из этого поста Роджером Кнаппом. Единственное требование состоит в том, что оба процесса должны иметь одинаковую битность. Вы не можете отлаживать 32-битный процесс из 64-битного процесса или наоборот.
Использование:
источник
Я искал решение этой проблемы, которое не требовало бы неуправляемого кода. Я также не смог использовать стандартное перенаправление ввода / вывода, потому что это было приложение Windows Forms.
Моим решением было создать именованный канал в родительском процессе, а затем подключить дочерний процесс к тому же каналу. Если родительский процесс завершается, то канал разрывается, и дочерний процесс может обнаружить это.
Ниже приведен пример использования двух консольных приложений:
родитель
ребенок
источник
Используйте обработчики событий для создания ловушек в нескольких сценариях выхода:
источник
Просто моя версия 2018 года. Используйте его в стороне от вашего метода Main ().
источник
Я вижу два варианта:
источник
Я создал дочернюю библиотеку управления процессами, где родительский процесс и дочерний процесс отслеживаются с помощью двунаправленного канала WCF. Если либо дочерний процесс завершается, либо родительский процесс завершает работу друг друга, это уведомляется. Также доступен помощник отладчика, который автоматически присоединяет отладчик VS к запущенному дочернему процессу.
Сайт проекта:
http://www.crawler-lib.net/child-processes
Пакеты NuGet:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
источник
Вызовите job.AddProcess лучше сделать после запуска процесса:
При вызове AddProcess до завершения дочерние процессы не уничтожаются. (Windows 7 SP1)
источник
Еще одно дополнение к изобилию предлагаемых решений ....
Проблема многих из них заключается в том, что они полагаются на то, что родительский и дочерний процессы завершают свою работу упорядоченным образом, что не всегда верно в процессе разработки. Я обнаружил, что мой дочерний процесс часто становился осиротевшим всякий раз, когда я завершал родительский процесс в отладчике, что требовало от меня уничтожить потерянный процесс (-ы) с помощью диспетчера задач, чтобы перестроить мое решение.
Решение: передать идентификатор родительского процесса в командной строке (или даже менее инвазивно, в переменных среды) дочернего процесса.
В родительском процессе идентификатор процесса доступен как:
В дочернем процессе:
У меня два мнения о том, является ли это приемлемой практикой в производственном коде. С одной стороны, этого никогда не должно случиться. Но с другой стороны, это может означать разницу между перезапуском процесса и перезагрузкой рабочего сервера. И то, что никогда не должно случаться, часто происходит.
И это, безусловно, полезно при отладке проблем с завершением работы.
источник