Qual pode ser o motivo do meu processo interrompido enquanto aguardo a saída?
Esse código precisa iniciar o script do powershell, que executa várias ações, por exemplo, iniciar a recompilação do código via MSBuild, mas provavelmente o problema é que gera muita saída e esse código fica travado enquanto espera para sair, mesmo após o script do shell de energia ter sido executado corretamente
é meio "estranho" porque às vezes esse código funciona bem e às vezes fica preso.
O código trava em:
process.WaitForExit (ProcessTimeOutMiliseconds);
O script do PowerShell é executado em 1-2seg, enquanto o tempo limite é 19seg.
public static (bool Success, string Logs) ExecuteScript(string path, int ProcessTimeOutMiliseconds, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var outputWaitHandle = new AutoResetEvent(false))
using (var errorWaitHandle = new AutoResetEvent(false))
{
try
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
process.OutputDataReceived += (sender, e) =>
{
if (e.Data == null)
{
outputWaitHandle.Set();
}
else
{
output.AppendLine(e.Data);
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (e.Data == null)
{
errorWaitHandle.Set();
}
else
{
error.AppendLine(e.Data);
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(ProcessTimeOutMiliseconds);
var logs = output + Environment.NewLine + error;
return process.ExitCode == 0 ? (true, logs) : (false, logs);
}
}
finally
{
outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
}
}
}
Roteiro:
start-process $args[0] App.csproj -Wait -NoNewWindow
[string]$sourceDirectory = "\bin\Debug\*"
[int]$count = (dir $sourceDirectory | measure).Count;
If ($count -eq 0)
{
exit 1;
}
Else
{
exit 0;
}
Onde
$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
Editar
Na solução da @ ingen, adicionei um pequeno invólucro que tenta executar novamente o MS Build desligado
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
var current = 0;
int attempts_count = 5;
bool _local_success = false;
string _local_logs = "";
while (attempts_count > 0 && _local_success == false)
{
Console.WriteLine($"Attempt: {++current}");
InternalExecuteScript(path, processTimeOutMilliseconds, out _local_logs, out _local_success, args);
attempts_count--;
}
success = _local_success;
logs = _local_logs;
}
Onde InternalExecuteScript
está o código da ingen
Rx
abordagem funcionou (como não esgotou o tempo limite) mesmo com o processo disperso do MSBuild por aí, levando a uma espera indefinida? interessado em saber como isso foi tratadoRespostas:
Vamos começar com uma recapitulação da resposta aceita em um post relacionado.
Mesmo a resposta aceita, no entanto, luta com a ordem de execução em certos casos.
É nesse tipo de situação, em que você deseja orquestrar vários eventos, que Rx realmente brilha.
Observe que a implementação .NET do Rx está disponível como o pacote System.Reactive NuGet.
Vamos nos aprofundar para ver como o Rx facilita o trabalho com eventos.
FromEventPattern
permite mapear ocorrências distintas de um evento para um fluxo unificado (também conhecido como observável). Isso nos permite manipular os eventos em um pipeline (com semântica semelhante ao LINQ). ASubscribe
sobrecarga usada aqui é fornecida com umAction<EventPattern<...>>
e umAction<Exception>
. Sempre que o evento observado é gerado, ésender
eargs
será envolvidoEventPattern
e pressionado através doAction<EventPattern<...>>
. Quando uma exceção é gerada no pipeline,Action<Exception>
é usada.Uma das desvantagens do
Event
padrão, claramente ilustrada neste caso de uso (e por todas as soluções alternativas na postagem mencionada), é que não é aparente quando / onde cancelar a inscrição dos manipuladores de eventos.Com o Rx, retornamos
IDisposable
quando fazemos uma assinatura. Quando descartamos, efetivamente encerramos a assinatura. Com a adição doDisposeWith
método de extensão (emprestado do RxUI ), podemos adicionar váriosIDisposable
s aCompositeDisposable
(nomeadosdisposables
nos exemplos de código). Quando terminarmos, podemos encerrar todas as assinaturas com uma chamada paradisposables.Dispose()
.Certamente, não há nada que possamos fazer com o Rx que não possamos fazer com o vanilla .NET. O código resultante é muito mais fácil de se raciocinar, uma vez que você se adaptou à maneira funcional de pensar.
Já discutimos a primeira parte, onde mapeamos nossos eventos para observáveis, para que possamos pular diretamente para a parte carnuda. Aqui atribuímos nosso observável à
processExited
variável, porque queremos usá-lo mais de uma vez.Primeiro, quando a ativamos, ligando
Subscribe
. E mais tarde, quando queremos "aguardar" seu primeiro valor.Um dos problemas com o OP é que ele pressupõe
process.WaitForExit(processTimeOutMiliseconds)
que o processo será encerrado quando o tempo limite expirar. Do MSDN :Em vez disso, quando atinge o tempo limite, ele simplesmente retorna o controle ao thread atual (ou seja, para de bloquear). Você precisa forçar manualmente a rescisão quando o processo atingir o tempo limite. Para saber quando o tempo limite ocorreu, podemos mapear o
Process.Exited
evento para umprocessExited
observável para processamento. Dessa forma, podemos preparar a entrada para oDo
operador.O código é bastante auto-explicativo. Se
exitedSuccessfully
o processo tiver terminado normalmente. Caso contrárioexitedSuccessfully
, a rescisão precisará ser forçada. Note-se queprocess.Kill()
é executado de forma assíncrona, Ref observações . No entanto, ligarprocess.WaitForExit()
imediatamente abrirá a possibilidade de conflitos novamente. Portanto, mesmo no caso de terminação forçada, é melhor deixar todos os materiais descartáveis limpos quando ousing
escopo terminar, pois a saída pode ser considerada interrompida / corrompida de qualquer maneira.A
try catch
construção é reservada para o caso excepcional (sem trocadilhos) em que você se alinhouprocessTimeOutMilliseconds
com o tempo real necessário para a conclusão do processo. Em outras palavras, uma condição de corrida ocorre entre oProcess.Exited
evento e o cronômetro. A possibilidade disso acontecer é novamente ampliada pela natureza assíncrona deprocess.Kill()
. Eu o encontrei uma vez durante o teste.Para completar, o
DisposeWith
método de extensão.fonte
ExecuteScriptRx
alçashangs
perfeitamente. Infelizmente, travamentos ainda acontecem, mas eu apenas adicionei um pequeno wrapper sobre o seuExecuteScriptRx
que executaRetry
e, em seguida, ele executa bem. O motivo de travamentos do MSBUILD pode ser a resposta @Clint. PS: Esse código me fez sentir estúpido <lol> É a primeira vez que vejoSystem.Reactive.Linq;
Para o benefício dos leitores, vou dividir isso em 2 seções
Seção A: Problema e como lidar com cenários semelhantes
Seção B: Recriação de problemas e solução
Seção A: Problema
No seu código:
Process.WaitForExit(ProcessTimeOutMiliseconds);
Com isso, você está esperandoProcess
para Timeout ou Exit , que já acontece primeiro .OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
eerrorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
com isso você está esperandoOutputData
&ErrorData
operação de leitura transmissão para sinalizar sua completaProcess.ExitCode == 0
Obtém o status do processo ao sairDiferentes configurações e suas advertências:
ObjectDisposedException()
Process.ExitCode
resultando no erroSystem.InvalidOperationException: Process must exit before requested information can be determined
.Eu testei esse cenário mais de uma dúzia de vezes e funciona bem, as seguintes configurações foram usadas durante o teste
Código atualizado
EDITAR:
Depois de horas brincando com o MSBuild, finalmente pude reproduzir o problema no meu sistema
Seção B: Recreação e solução de problemas
Consegui resolver isso de duas maneiras
Gerar o processo MSBuild indiretamente através do CMD
Continue usando o MSBuild, mas certifique-se de definir o nodeReuse como False
Mesmo que a compilação paralela não esteja ativada, você ainda pode impedir que seu processo seja interrompido
WaitForExit
iniciando a compilação via CMD e, portanto, não cria uma dependência direta no processo de compilaçãoA segunda abordagem é preferida, pois você não deseja que muitos nós do MSBuild estejam por aí.
fonte
"-nr:False","-m:3"
parece ter corrigido o comportamento de suspensão do MSBuild, o queRx solution
tornou todo o processo um tanto confiável (o tempo vai mostrar). Eu gostaria de poder aceitar as duas respostas ou dar duas recompensasRx
abordagem na outra solução seria capaz de resolver o problema sem aplicar-nr:False" ,"-m:3"
. No meu entendimento, ele lida com a espera indefinida de impasses e outras coisas que eu havia abordado na seção 1. E a causa raiz na Seção 2 é o que eu acredito ser a causa raiz do problema que você enfrentou;) Eu posso estar errado e é por isso que Eu perguntei, só o tempo dirá ... Saúde !!O problema é que, se você redirecionar o StandardOutput e / ou o StandardError, o buffer interno poderá ficar cheio.
Para resolver os problemas mencionados acima, você pode executar o processo em threads separados. Não uso WaitForExit, utilizo o evento encerrado pelo processo que retornará o ExitCode do processo de forma assíncrona, garantindo que ele tenha sido concluído.
O código acima é testado em batalha, chamando FFMPEG.exe com argumentos de linha de comando. Eu estava convertendo arquivos mp4 para arquivos mp3 e fazendo mais de 1000 vídeos por vez sem falhar. Infelizmente, não tenho experiência direta com shell de energia, mas espero que isso ajude.
fonte
BegingOutputReadline
e, em seguida, executarReadToEndAsync
emStandardError
?Não tenho certeza se esse é o seu problema, mas olhando para o MSDN parece haver alguma estranheza com o WaitForExit sobrecarregado quando você está redirecionando a saída de forma assíncrona. O artigo MSDN recomenda chamar o WaitForExit que não usa argumentos após chamar o método sobrecarregado.
Página do Google Docs localizada aqui. Texto relevante:
A modificação do código pode ser algo como isto:
fonte
process.WaitForExit()
conforme indicado pelos comentários a esta resposta .