De este foro , crédito a 'Josh'.
Application.Quit()
y Process.Kill()
son posibles soluciones, pero han demostrado ser poco confiables. Cuando su aplicación principal muere, todavía le quedan procesos secundarios en ejecución. Lo que realmente queremos es que los procesos secundarios mueran tan pronto como muera el proceso principal.
La solución es utilizar "objetos de trabajo" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
La idea es crear un "objeto de trabajo" para su aplicación principal y registrar sus procesos secundarios con el objeto de trabajo. Si el proceso principal muere, el sistema operativo se encargará de finalizar los procesos secundarios.
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);
}
}
Mirando al constructor ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
La clave aquí es configurar el objeto de trabajo correctamente. En el constructor estoy estableciendo los "límites" a 0x2000, que es el valor numérico para JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
MSDN define esta bandera como:
Hace que todos los procesos asociados con el trabajo finalicen cuando se cierra el último identificador del trabajo.
Una vez que esta clase está configurada ... solo tiene que registrar cada proceso secundario con el trabajo. Por ejemplo:
[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
origina? ¿Se importa esto desde kernel32.dll? Hay una firma coincidente allí, pero no la está importando explícitamente como las otras funciones API.Esta respuesta comenzó con la excelente respuesta de @Matt Howells más otras (ver enlaces en el código a continuación). Mejoras:
extendedInfoPtr
CreateJobObject
(usando Windows 10, Visual Studio 2015, 32 bits).Aquí se explica cómo usar este código:
Para admitir Windows 7 requiere:
En mi caso, no necesitaba admitir Windows 7, así que tengo una simple verificación en la parte superior del constructor estático a continuación.
Probé cuidadosamente las versiones de 32 bits y 64 bits de las estructuras comparando mediante programación las versiones administradas y nativas entre sí (el tamaño general y las compensaciones para cada miembro).
He probado este código en Windows 7, 8 y 10.
fuente
Esta publicación pretende ser una extensión de la respuesta de @Matt Howells, específicamente para aquellos que tienen problemas al usar Job Objects en Vista o Win7 , especialmente si obtiene un error de acceso denegado ('5') al llamar a AssignProcessToJobObject.
tl; dr
Para garantizar la compatibilidad con Vista y Win7, agregue el siguiente manifiesto al proceso padre .NET:
Tenga en cuenta que cuando agregue un nuevo manifiesto en Visual Studio 2012, ya contendrá el fragmento anterior, por lo que no necesita copiarlo desde escuchar. También incluirá un nodo para Windows 8.
explicación completa
Su asociación de trabajo fallará con un error de acceso denegado si el proceso que está iniciando ya está asociado con otro trabajo. Ingrese al Asistente de compatibilidad de programas, que, a partir de Windows Vista, asignará todo tipo de procesos a sus propios trabajos.
En Vista, puede marcar su aplicación para que sea excluida de PCA simplemente incluyendo un manifiesto de aplicación. Visual Studio parece hacer esto para las aplicaciones .NET automáticamente, por lo que está bien allí.
Un manifiesto simple ya no lo corta en Win7. [1] Allí, debes especificar específicamente que eres compatible con Win7 con la etiqueta en tu manifiesto. [2]
Esto me llevó a preocuparme por Windows 8. ¿Tendré que cambiar mi manifiesto una vez más? Aparentemente hay una ruptura en las nubes, ya que Windows 8 ahora permite que un proceso pertenezca a múltiples trabajos. [3] Por lo tanto, aún no lo he probado, pero imagino que esta locura terminará ahora si simplemente incluye un manifiesto con la información del sistema operativo compatible.
Consejo 1 : Si está desarrollando una aplicación .NET con Visual Studio, como lo hice yo, aquí [4] hay algunas buenas instrucciones sobre cómo personalizar el manifiesto de su aplicación.
Consejo 2 : Tenga cuidado al iniciar su aplicación desde Visual Studio. Descubrí que, después de agregar el manifiesto apropiado, todavía tenía problemas con PCA al iniciar desde Visual Studio, incluso si usaba Iniciar sin depurar. Sin embargo, el lanzamiento de mi aplicación desde Explorer funcionó. Después de agregar manualmente devenv para la exclusión de PCA usando el registro, las aplicaciones que usaban Job Objects de VS también comenzaron a funcionar. [5]
Consejo 3 : Si alguna vez desea saber si PCA es su problema, intente iniciar su aplicación desde la línea de comandos o copie el programa en una unidad de red y ejecútelo desde allí. PCA se deshabilita automáticamente en esos contextos.
[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-because-we-changed-the-rules-on-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 : "Un proceso puede estar asociado con más de un trabajo en Windows 8"
[4] ¿Cómo puedo incrustar un manifiesto de aplicación en una aplicación usando VS2008?
[5] ¿Cómo detener el depurador de Visual Studio que comienza mi proceso en un objeto de trabajo?
fuente
Aquí hay una alternativa que puede funcionar para algunos cuando tiene control del código que ejecuta el proceso secundario. El beneficio de este enfoque es que no requiere ninguna llamada nativa de Windows.
La idea básica es redirigir la entrada estándar del niño a un flujo cuyo otro extremo está conectado al padre y usar ese flujo para detectar cuándo el padre se ha ido. Cuando utiliza
System.Diagnostics.Process
para iniciar el elemento secundario, es fácil asegurarse de que su entrada estándar se redirija:Y luego, en el proceso secundario, aproveche el hecho de que
Read
s del flujo de entrada estándar siempre regresará con al menos 1 byte hasta que se cierre el flujo, cuando comenzarán a devolver 0 bytes. A continuación se muestra un resumen de la forma en que terminé haciendo esto; my way también usa una bomba de mensajes para mantener el hilo principal disponible para otras cosas que no sean mirar estándar, pero este enfoque general podría usarse sin bombas de mensajes también.Advertencias a este enfoque:
el .exe secundario real que se inicia debe ser una aplicación de consola para que permanezca adjunto a stdin / out / err. Como en el ejemplo anterior, adapté fácilmente mi aplicación existente que usaba una bomba de mensajes (pero no mostraba una GUI) simplemente creando un pequeño proyecto de consola que hacía referencia al proyecto existente, instanciando el contexto de mi aplicación y llamando
Application.Run()
dentro delMain
método del consola .exe.Técnicamente, esto simplemente indica el proceso hijo cuando el padre sale, por lo que funcionará si el proceso padre salió normalmente o se bloqueó, pero aún depende de los procesos hijos realizar su propio apagado. Esto puede o no ser lo que quieres ...
fuente
Una forma es pasar el PID del proceso padre al niño. El hijo sondeará periódicamente si el proceso con el pid especificado existe o no. Si no, simplemente se cerrará.
También puede usar el método Process.WaitForExit en el método secundario para recibir una notificación cuando finalice el proceso principal, pero podría no funcionar en el caso del Administrador de tareas.
fuente
Process.Start(string fileName, string arguments)
Existe otro método relevante, fácil y efectivo, para finalizar los procesos secundarios al finalizar el programa. Puede implementar y adjuntar un depurador desde el padre; cuando finaliza el proceso principal, el sistema operativo eliminará los procesos secundarios. Puede ir en ambos sentidos adjuntando un depurador al padre desde el hijo (tenga en cuenta que solo puede adjuntar un depurador a la vez). Puede encontrar más información sobre el tema aquí .
Aquí tiene una clase de utilidad que inicia un nuevo proceso y le agrega un depurador. Ha sido adaptado de esta publicación por Roger Knapp. El único requisito es que ambos procesos deben compartir el mismo bitness. No puede depurar un proceso de 32 bits desde un proceso de 64 bits o viceversa.
Uso:
fuente
Estaba buscando una solución a este problema que no requiriera código no administrado. Tampoco pude usar la redirección de entrada / salida estándar porque era una aplicación de formularios Windows Forms.
Mi solución fue crear una tubería con nombre en el proceso primario y luego conectar el proceso secundario a la misma tubería. Si el proceso padre se cierra, la tubería se rompe y el niño puede detectar esto.
A continuación se muestra un ejemplo con dos aplicaciones de consola:
Padre
Niño
fuente
Use controladores de eventos para hacer enlaces en algunos escenarios de salida:
fuente
Solo mi versión 2018. Úselo a un lado de su método Main ().
fuente
Veo dos opciones:
fuente
He creado una biblioteca de gestión de procesos secundarios donde el proceso principal y el proceso secundario se supervisan debido a una tubería WCF bidireccional. Si el proceso secundario finaliza o el proceso primario finaliza entre sí, se notificará. También hay un asistente de depuración disponible que conecta automáticamente el depurador VS al proceso secundario iniciado
Sitio del proyecto:
http://www.crawler-lib.net/child-processes
Paquetes NuGet:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
fuente
llame a job.AddProcess mejor que hacer después del inicio del proceso:
Al llamar a AddProcess antes de finalizar, los procesos secundarios no se eliminan. (Windows 7 SP1)
fuente
Otra adición a la abundante riqueza de soluciones propuestas hasta ahora ...
El problema con muchos de ellos es que dependen del proceso padre e hijo para cerrar de manera ordenada, lo que no siempre es cierto cuando el desarrollo está en marcha. Descubrí que mi proceso hijo a menudo se quedaba huérfano cada vez que terminaba el proceso padre en el depurador, lo que requería que matara los procesos huérfanos con el Administrador de tareas para reconstruir mi solución.
La solución: Pase la identificación del proceso principal en la línea de comandos (o incluso menos invasiva, en las variables de entorno) del proceso secundario.
En el proceso padre, la ID del proceso está disponible como:
En el proceso hijo:
Tengo dos opiniones sobre si esta es una práctica aceptable en el código de producción. Por un lado, esto nunca debería suceder. Pero, por otro lado, puede significar la diferencia entre reiniciar un proceso y reiniciar un servidor de producción. Y lo que nunca debería suceder a menudo lo hace.
Y seguro que es útil al depurar problemas de apagado ordenado.
fuente