Deste fórum , o crédito ao 'Josh'.
Application.Quit()
e Process.Kill()
são possíveis soluções, mas provaram não ser confiáveis. Quando seu aplicativo principal morre, você ainda fica com os processos filhos em execução. O que realmente queremos é que os processos filhos morram assim que o processo principal morre.
A solução é usar "objetos de trabalho" http://msdn.microsoft.com/en-us/library/ms682409(VS.85).aspx .
A idéia é criar um "objeto de trabalho" para o aplicativo principal e registrar os processos filhos no objeto de trabalho. Se o processo principal morrer, o sistema operacional cuidará de encerrar os processos filhos.
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);
}
}
Olhando para o construtor ...
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
A chave aqui é configurar o objeto de trabalho corretamente. No construtor, estou configurando os "limites" para 0x2000, que é o valor numérico paraJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
.
O MSDN define esse sinalizador como:
Faz com que todos os processos associados ao trabalho sejam finalizados quando o último identificador do trabalho é fechado.
Depois que essa classe é configurada ... você apenas precisa registrar cada processo filho no trabalho. Por exemplo:
[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
originam? Isso é importado do kernel32.dll? Há uma assinatura correspondente lá, mas você não a está importando explicitamente como as outras funções da API.Esta resposta começou com a excelente resposta de @Matt Howells e outras (consulte os links no código abaixo). Melhorias:
extendedInfoPtr
CreateJobObject
(usando o Windows 10, Visual Studio 2015, 32 bits).Veja como usar esse código:
Para oferecer suporte ao Windows 7, é necessário:
No meu caso, não precisava oferecer suporte ao Windows 7, portanto, tenho uma verificação simples na parte superior do construtor estático abaixo.
Testei cuidadosamente as versões de 32 e 64 bits das estruturas comparando programaticamente as versões gerenciada e nativa entre si (o tamanho geral e as compensações de cada membro).
Testei esse código no Windows 7, 8 e 10.
fonte
Esta publicação pretende ser uma extensão da resposta do @Matt Howells, especificamente para aqueles que têm problemas com o uso de objetos de trabalho no Vista ou no Win7 , especialmente se você receber um erro de acesso negado ('5') ao chamar AssignProcessToJobObject.
tl; dr
Para garantir a compatibilidade com o Vista e o Win7, adicione o seguinte manifesto ao processo pai do .NET:
Observe que, quando você adiciona um novo manifesto no Visual Studio 2012, ele já contém o snippet acima, para que você não precise copiá-lo do hear. Também incluirá um nó para o Windows 8.
explicação completa
Sua associação ao trabalho falhará com um erro de acesso negado se o processo que você está iniciando já estiver associado a outro trabalho. Digite o Assistente de Compatibilidade de Programa, que, a partir do Windows Vista, atribuirá todos os tipos de processos a seus próprios trabalhos.
No Vista, você pode marcar seu aplicativo para ser excluído do PCA simplesmente incluindo um manifesto de aplicativo. O Visual Studio parece fazer isso para aplicativos .NET automaticamente, então você está bem.
Um manifesto simples não o corta mais no Win7. [1] Lá, é necessário especificar especificamente que você é compatível com o Win7 com a tag no seu manifesto. [2]
Isso me levou a me preocupar com o Windows 8. Terei que mudar meu manifesto mais uma vez? Aparentemente, há uma quebra nas nuvens, pois o Windows 8 agora permite que um processo pertença a vários trabalhos. [3] Então, ainda não testei, mas imagino que essa loucura acabará agora se você simplesmente incluir um manifesto com as informações suportadas do OS.
Dica 1 : se você está desenvolvendo um aplicativo .NET com o Visual Studio, como eu estava, aqui estão algumas instruções legais sobre como personalizar o manifesto do aplicativo.
Dica 2 : tenha cuidado ao iniciar seu aplicativo no Visual Studio. Descobri que, depois de adicionar o manifesto apropriado, ainda havia problemas com o PCA ao iniciar a partir do Visual Studio, mesmo se eu usasse Iniciar sem Depuração. Iniciar meu aplicativo no Explorer funcionou, no entanto. Após adicionar manualmente o devenv para exclusão do PCA usando o registro, o início de aplicativos que usavam Objetos de Trabalho do VS também começou a funcionar. [5]
Dica 3 : Se você quiser saber se o PCA é seu problema, tente iniciar o aplicativo a partir da linha de comandos ou copie o programa em uma unidade de rede e execute-o a partir daí. O PCA é automaticamente desativado nesses 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-porque-mudamos-as-regras-em-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 : "Um processo pode ser associado a mais de um trabalho no Windows 8"
[4] Como posso incorporar um manifesto de aplicativo em um aplicativo usando o VS2008?
[5] Como parar o depurador do Visual Studio que inicia o meu processo em um objeto de trabalho?
fonte
Aqui está uma alternativa que pode funcionar para alguns quando você tem controle do código que o processo filho executa. O benefício dessa abordagem é que ela não requer nenhuma chamada nativa do Windows.
A idéia básica é redirecionar a entrada padrão da criança para um fluxo cuja outra extremidade está conectada ao pai e usar esse fluxo para detectar quando o pai foi embora. Quando você
System.Diagnostics.Process
inicia o filho, é fácil garantir que sua entrada padrão seja redirecionada:E, no processo filho, aproveite o fato de que
Read
s do fluxo de entrada padrão sempre retornam com pelo menos 1 byte até que o fluxo seja fechado, quando começarão a retornar 0 bytes. Um resumo da maneira como acabei fazendo isso está abaixo; meu modo também usa uma bomba de mensagens para manter o encadeamento principal disponível para outras coisas além da observação padrão, mas essa abordagem geral também pode ser usada sem bombas de mensagens.Advertências para esta abordagem:
o .exe filho real que é iniciado deve ser um aplicativo de console para permanecer anexado a stdin / out / err. Como no exemplo acima, adaptei facilmente meu aplicativo existente que usava uma bomba de mensagens (mas não mostrava uma GUI) criando apenas um pequeno projeto de console que referenciava o projeto existente, instanciando o contexto do meu aplicativo e chamando
Application.Run()
dentro doMain
método console .exe.Tecnicamente, isso apenas sinaliza o processo filho quando o pai sai, portanto, ele funcionará se o processo pai saiu normalmente ou travou, mas ainda depende do processo filho executar seu próprio desligamento. Isso pode ou não ser o que você deseja ...
fonte
Uma maneira é passar o PID do processo pai para o filho. A criança pesquisará periodicamente se o processo com o pid especificado existe ou não. Caso contrário, apenas sairá.
Você também pode usar o método Process.WaitForExit no método filho para ser notificado quando o processo pai terminar, mas pode não funcionar no caso do Gerenciador de Tarefas.
fonte
Process.Start(string fileName, string arguments)
Existe outro método relevante, fácil e eficaz, para concluir os processos filhos na finalização do programa. Você pode implementar e anexar um depurador a eles a partir do pai; quando o processo pai terminar, os processos filhos serão eliminados pelo sistema operacional. Ele pode seguir os dois sentidos ao anexar um depurador ao pai do filho (observe que você pode anexar apenas um depurador por vez). Você pode encontrar mais informações sobre o assunto aqui .
Aqui você tem uma classe de utilitário que inicia um novo processo e anexa um depurador a ele. Foi adaptado deste post por Roger Knapp. O único requisito é que ambos os processos precisem compartilhar a mesma testemunha. Você não pode depurar um processo de 32 bits de um processo de 64 bits ou vice-versa.
Uso:
fonte
Eu estava procurando uma solução para esse problema que não exigisse código não gerenciado. Também não pude usar o redirecionamento de entrada / saída padrão porque era um aplicativo Windows Forms.
Minha solução foi criar um pipe nomeado no processo pai e conectar o processo filho ao mesmo pipe. Se o processo pai sair, o tubo será quebrado e o filho poderá detectá-lo.
Abaixo está um exemplo usando dois aplicativos de console:
Pai
Criança
fonte
Use manipuladores de eventos para fazer ganchos em alguns cenários de saída:
fonte
Apenas minha versão de 2018. Use-o de lado o seu método Main ().
fonte
Eu vejo duas opções:
fonte
Criei uma biblioteca de gerenciamento de processos filho em que o processo pai e o processo filho são monitorados devido a um canal WCF bidirecional. Se o processo filho terminar ou o processo pai terminar, será notificado. Há também um auxiliar de depurador disponível, que anexa automaticamente o depurador do VS ao processo filho iniciado
Projetar site:
http://www.crawler-lib.net/child-processes
Pacotes NuGet:
https://www.nuget.org/packages/ChildProcesses https://www.nuget.org/packages/ChildProcesses.VisualStudioDebug/
fonte
chame job.AddProcess para fazer melhor após o início do processo:
Ao chamar AddProcess antes da finalização, os processos filhos não são eliminados. (Windows 7 SP1)
fonte
Mais uma adição à abundante riqueza de soluções propostas até agora ...
O problema com muitos deles é que eles dependem do processo pai e filho para desligar de maneira ordenada, o que nem sempre é verdade quando o desenvolvimento está em andamento. Descobri que meu processo filho costumava ficar órfão sempre que eu encerrava o processo pai no depurador, o que exigia que eu matasse o (s) processo (s) órfão (s) com o Gerenciador de Tarefas para reconstruir minha solução.
A solução: passe o ID do processo pai na linha de comando (ou até menos invasiva, nas variáveis de ambiente) do processo filho.
No processo pai, o ID do processo está disponível como:
No processo filho:
Penso se isso é uma prática aceitável no código de produção. Por um lado, isso nunca deve acontecer. Mas, por outro lado, pode significar a diferença entre reiniciar um processo e reiniciar um servidor de produção. E o que nunca deveria acontecer frequentemente acontece.
E com certeza é útil ao depurar problemas de desligamento ordenado.
fonte