Z tego forum , uznanie dla „Josha”.
Application.Quit()
i Process.Kill()
są możliwymi rozwiązaniami, ale okazały się niewiarygodne. Kiedy główna aplikacja umiera, nadal pozostają uruchomione procesy potomne. To, czego naprawdę chcemy, to aby dziecko umierało, gdy tylko główny proces umrze.
Rozwiązaniem jest użycie „obiektów zadań” http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
Pomysł polega na utworzeniu „obiektu zadania” dla głównej aplikacji i zarejestrowaniu procesów podrzędnych w obiekcie zadania. Jeśli główny proces umrze, system operacyjny zajmie się zakończeniem procesów potomnych.
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);
}
}
Patrząc na konstruktora ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
Kluczem jest tutaj prawidłowe ustawienie obiektu zadania. W konstruktorze ustawiam „limity” na 0x2000, co jest wartością liczbową JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
MSDN definiuje tę flagę jako:
Powoduje, że wszystkie procesy związane z zadaniem kończą się, gdy ostatnie dojście do zadania zostaje zamknięte.
Po skonfigurowaniu tej klasy ... wystarczy zarejestrować każdy proces potomny w zadaniu. Na przykład:
[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
? Czy to jest importowane z kernel32.dll? Jest tam pasujący podpis, ale nie importujesz go jawnie, tak jak inne funkcje API.Ta odpowiedź zaczęła się od doskonałej odpowiedzi @Matt Howells oraz innych (zobacz linki w poniższym kodzie). Ulepszenia:
extendedInfoPtr
CreateJobObject
(w systemie Windows 10, Visual Studio 2015, 32-bitowy).Oto jak używać tego kodu:
Do obsługi systemu Windows 7 wymagane są:
W moim przypadku nie musiałem obsługiwać systemu Windows 7, więc mam proste sprawdzenie u góry konstruktora statycznego poniżej.
Dokładnie przetestowałem zarówno 32-bitowe, jak i 64-bitowe wersje struktur, porównując programowo wersje zarządzane i natywne ze sobą (całkowity rozmiar oraz przesunięcia dla każdego elementu członkowskiego).
Przetestowałem ten kod na Windows 7, 8 i 10.
źródło
Ten post ma na celu rozszerzenie odpowiedzi @Matt Howells, szczególnie dla tych, którzy mają problemy z używaniem Job Objects pod Vista lub Win7 , zwłaszcza jeśli otrzymujesz błąd odmowy dostępu ('5') podczas wywoływania AssignProcessToJobObject.
tl; dr
Aby zapewnić zgodność z systemami Vista i Win7, dodaj następujący manifest do procesu nadrzędnego .NET:
Zwróć uwagę, że po dodaniu nowego manifestu w programie Visual Studio 2012 będzie on już zawierał powyższy fragment, więc nie musisz go kopiować z funkcji Hear. Będzie również zawierał węzeł dla systemu Windows 8.
pełne wyjaśnienie
Twoje powiązanie zadania zakończy się niepowodzeniem i zostanie wyświetlony błąd odmowy dostępu, jeśli uruchamiany proces jest już powiązany z innym zadaniem. Wejdź do Asystenta zgodności programów, który począwszy od systemu Windows Vista przypisze wszystkie rodzaje procesów do własnych zadań.
W systemie Vista możesz oznaczyć swoją aplikację jako wykluczoną z PCA, po prostu dołączając manifest aplikacji. Wydaje się, że Visual Studio robi to automatycznie dla aplikacji .NET, więc wszystko w porządku.
Prosty manifest nie działa już w systemie Win7. [1] Tam musisz konkretnie określić, że jesteś kompatybilny z Win7 za pomocą tagu w swoim manifeście. [2]
To spowodowało, że zacząłem martwić się systemem Windows 8. Czy będę musiał ponownie zmienić manifest? Najwyraźniej w chmurach jest przerwa, ponieważ Windows 8 pozwala teraz na przynależność procesu do wielu zadań. [3] Więc jeszcze tego nie testowałem, ale wyobrażam sobie, że to szaleństwo minie, jeśli po prostu dołączysz manifest z informacjami o obsługiwanym systemie operacyjnym.
Wskazówka 1 : Jeśli tworzysz aplikację .NET w Visual Studio, tak jak ja, tutaj [4] znajdziesz kilka przydatnych instrukcji, jak dostosować manifest aplikacji.
Porada 2 : Zachowaj ostrożność podczas uruchamiania aplikacji z programu Visual Studio. Stwierdziłem, że po dodaniu odpowiedniego manifestu nadal miałem problemy z PCA podczas uruchamiania z Visual Studio, nawet jeśli użyłem Start bez debugowania. Uruchomienie mojej aplikacji z Eksploratora zadziałało jednak. Po ręcznym dodaniu devenv w celu wykluczenia z PCA przy użyciu rejestru, zaczęło działać również uruchamianie aplikacji korzystających z obiektów zadań z VS. [5]
Wskazówka 3 : Jeśli kiedykolwiek chcesz wiedzieć, czy problem dotyczy PCA, spróbuj uruchomić aplikację z wiersza poleceń lub skopiuj program na dysk sieciowy i uruchom go stamtąd. W tych kontekstach PCA jest automatycznie wyłączane.
[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-ponieważ-zmieniliśmy-zasady-na-ciebie.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 : „Proces może być powiązany z więcej niż jednym zadaniem w systemie Windows 8”
[4] Jak mogę osadzić manifest aplikacji w aplikacji przy użyciu VS2008?
[5] Jak zatrzymać debuger programu Visual Studio, uruchamiając mój proces w obiekcie zadania?
źródło
Oto alternatywa, która może zadziałać dla niektórych, gdy masz kontrolę nad kodem uruchamianym przez proces potomny. Zaletą tego podejścia jest to, że nie wymaga żadnych natywnych wywołań systemu Windows.
Podstawowym pomysłem jest przekierowanie standardowego wejścia dziecka do strumienia, którego drugi koniec jest połączony z rodzicem, i użycie tego strumienia do wykrycia, kiedy rodzic odszedł. Kiedy używasz
System.Diagnostics.Process
do uruchomienia dziecka, łatwo jest upewnić się, że jego standardowe wejście zostanie przekierowane:A następnie, w procesie potomnym, wykorzystaj fakt, że
Read
s ze standardowego strumienia wejściowego zawsze będzie zwracać co najmniej 1 bajt, dopóki strumień nie zostanie zamknięty, kiedy zaczną zwracać 0 bajtów. Poniżej znajduje się zarys sposobu, w jaki to zrobiłem; mój sposób używa również pompy wiadomości, aby główny wątek był dostępny dla rzeczy innych niż oglądanie standardu w, ale to ogólne podejście może być również używane bez pomp wiadomości.Ostrzeżenia dotyczące tego podejścia:
rzeczywisty uruchamiany podrzędny plik .exe musi być aplikacją konsolową, więc pozostaje dołączony do stdin / out / err. Jak w powyższym przykładzie, z łatwością dostosowałem moją istniejącą aplikację, która korzystała z pompy komunikatów (ale nie wyświetlała GUI), po prostu tworząc niewielki projekt konsoli, który odwoływał się do istniejącego projektu, tworząc instancję mojego kontekstu aplikacji i wywołując
Application.Run()
wewnątrzMain
metody konsola .exe.Technicznie rzecz biorąc, to tylko sygnalizuje procesowi podrzędnemu, kiedy wychodzi rodzic, więc będzie działać, niezależnie od tego, czy proces nadrzędny zakończył normalnie, czy się zawiesił, ale nadal to procesy potomne muszą wykonać własne zamknięcie. To może być to, czego chcesz, ale nie musi ...
źródło
Jednym ze sposobów jest przekazanie dziecku PID procesu rodzica. Dziecko będzie okresowo odpytywać, czy proces z określonym pid istnieje, czy nie. Jeśli nie, po prostu przestanie.
Możesz także użyć metody Process.WaitForExit w metodzie potomnej, aby otrzymać powiadomienie o zakończeniu procesu nadrzędnego, ale może to nie działać w przypadku Menedżera zadań.
źródło
Process.Start(string fileName, string arguments)
Jest jeszcze jedna odpowiednia metoda, łatwa i skuteczna, aby zakończyć procesy potomne po zakończeniu programu. Możesz zaimplementować i dołączyć do nich debugger od rodzica; po zakończeniu procesu nadrzędnego procesy potomne zostaną zabite przez system operacyjny. Może to działać w obie strony, dołączając debuger do rodzica od dziecka (pamiętaj, że możesz dołączyć tylko jeden debuger na raz). Więcej informacji na ten temat znajdziesz tutaj .
Tutaj masz klasę narzędziową, która uruchamia nowy proces i dołącza do niego debugger. Został zaadaptowany na podstawie tego postu przez Rogera Knappa. Jedynym wymaganiem jest to, że oba procesy muszą mieć tę samą bitowość. Nie można debugować procesu 32-bitowego z procesu 64-bitowego ani odwrotnie.
Stosowanie:
źródło
Szukałem rozwiązania tego problemu, które nie wymagałoby niezarządzanego kodu. Nie mogłem również użyć standardowego przekierowania wejścia / wyjścia, ponieważ była to aplikacja Windows Forms.
Moim rozwiązaniem było utworzenie nazwanego potoku w procesie nadrzędnym, a następnie połączenie procesu potomnego z tym samym potokiem. Jeśli proces macierzysty zostanie zamknięty, potok zostanie przerwany i dziecko może to wykryć.
Poniżej znajduje się przykład użycia dwóch aplikacji konsolowych:
Rodzic
Dziecko
źródło
Użyj programów obsługi zdarzeń, aby utworzyć punkty zaczepienia w kilku scenariuszach wyjścia:
źródło
Tylko moja wersja 2018. Użyj go poza swoją metodą Main ().
źródło
Widzę dwie opcje:
źródło
Utworzyłem bibliotekę zarządzania procesami podrzędnymi, w której proces nadrzędny i proces podrzędny są monitorowane z powodu dwukierunkowego potoku WCF. Jeśli albo proces potomny zostanie zakończony, albo proces nadrzędny, zostanie o tym powiadomiony. Dostępny jest również pomocnik debugera, który automatycznie dołącza debuger VS do uruchomionego procesu podrzędnego
Strona projektu:
http://www.crawler-lib.net/child-processes
Pakiety NuGet:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
źródło
call job.AddProcess lepiej zrobić po rozpoczęciu procesu:
Podczas wywoływania AddProcess przed zakończeniem procesy potomne nie są zabijane. (Windows 7 z dodatkiem SP1)
źródło
Kolejny dodatek do bogactwa dotychczas proponowanych rozwiązań ...
Problem z wieloma z nich polega na tym, że polegają na procesie rodziców i dziecka, aby zamknęli się w uporządkowany sposób, co nie zawsze jest prawdą, gdy rozwój jest w toku. Zauważyłem, że mój proces podrzędny był często osierocony za każdym razem, gdy kończyłem proces nadrzędny w debugerze, co wymagało ode mnie zabicia osieroconych procesów za pomocą Menedżera zadań w celu odbudowania rozwiązania.
Rozwiązanie: Przekaż identyfikator procesu nadrzędnego w wierszu poleceń (lub nawet mniej inwazyjnie w zmiennych środowiskowych) procesu potomnego.
W procesie nadrzędnym identyfikator procesu jest dostępny jako:
W procesie dziecięcym:
Nie mam zdania, czy jest to dopuszczalna praktyka w kodzie produkcyjnym. Z jednej strony to nigdy nie powinno mieć miejsca. Z drugiej strony może to oznaczać różnicę między ponownym uruchomieniem procesu a ponownym uruchomieniem serwera produkcyjnego. A to, co nigdy nie powinno się zdarzyć, często się dzieje.
I z pewnością jest przydatny podczas debugowania problemów z uporządkowanym zamykaniem.
źródło