Что может быть причиной того, что мой процесс завис в ожидании выхода?
Этот код должен запускать сценарий powershell, который внутри выполняет много действий, например, запускает перекомпиляцию кода через MSBuild, но, вероятно, проблема в том, что он генерирует слишком много выходных данных, и этот код застревает при ожидании выхода, даже после того, как сценарий Power Shell был выполнен правильно
это немного «странно», потому что иногда этот код работает нормально, а иногда просто застревает.
Код висит на:
process.WaitForExit (ProcessTimeOutMiliseconds);
Сценарий Powershell выполняется примерно за 1-2 секунды, а время ожидания составляет 19 секунд.
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);
}
}
}
Автор сценария:
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;
}
где
$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
редактировать
К решению @ ingen я добавил небольшую оболочку, которая пытается выполнить зависший MS Build
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;
}
Где InternalExecuteScript
находится код Ингена
Rx
подход работал (как и в нем не было тайм-аута) даже при случайном процессе MSBuild, ведущем к неопределенному ожиданию? интересно узнать, как с этим справилисьОтветы:
Давайте начнем с резюме принятого ответа в соответствующем посте.
Однако даже принятый ответ в определенных случаях борется с порядком исполнения.
Именно в таких ситуациях, когда вы хотите организовать несколько событий, Rx действительно сияет.
Обратите внимание, что .NET-реализация Rx доступна в виде пакета System.Reactive NuGet.
Давайте рассмотрим, как Rx облегчает работу с событиями.
FromEventPattern
позволяет нам отображать различные вхождения события в единый поток (также наблюдаемый). Это позволяет нам обрабатывать события в конвейере (с LINQ-подобной семантикой).Subscribe
Перегрузки используются здесь снабженыAction<EventPattern<...>>
иAction<Exception>
. Всякий раз, когда наблюдаемое событие возникает, оноsender
иargs
будет обернутоEventPattern
и протолкнуто черезAction<EventPattern<...>>
. Когда в конвейере возникает исключение,Action<Exception>
используется.Одним из недостатков
Event
шаблона, четко проиллюстрированного в этом случае использования (и всеми обходными путями в ссылочном посте), заключается в том, что неясно, когда и где отписать обработчики событий.С Rx мы возвращаемся,
IDisposable
когда мы делаем подписку. Когда мы избавляемся от этого, мы фактически прекращаем подписку. С добавлениемDisposeWith
метода расширения (заимствованного из RxUI ) мы можем добавить несколькоIDisposable
s к aCompositeDisposable
(названномуdisposables
в примерах кода). Когда мы все закончим, мы можем завершить все подписки одним вызовомdisposables.Dispose()
.Безусловно, мы ничего не можем сделать с Rx, чего бы мы не смогли сделать с vanilla .NET. Получившийся код будет намного проще рассуждать, если вы адаптируетесь к функциональному мышлению.
Мы уже обсуждали первую часть, где мы сопоставляем наши события с наблюдаемыми, поэтому мы можем перейти прямо к мясной части. Здесь мы назначаем нашу наблюдаемую
processExited
переменную, потому что мы хотим использовать ее более одного раза.Во-первых, когда мы его активируем, звоним
Subscribe
. А потом, когда мы хотим «дождаться» его первого значения.Одна из проблем с OP заключается в том, что предполагается, что он
process.WaitForExit(processTimeOutMiliseconds)
прервет процесс, когда закончится время ожидания. Из MSDN :Вместо этого, по истечении времени ожидания, он просто возвращает управление текущему потоку (то есть прекращает блокировку). Вы должны вручную принудительно завершить завершение, когда процесс истекает. Чтобы узнать, когда истекло время ожидания, мы можем сопоставить
Process.Exited
событие сprocessExited
наблюдаемой для обработки. Таким образом, мы можем подготовить ввод дляDo
оператора.Код довольно понятен. Если
exitedSuccessfully
процесс будет завершен изящно. Если нетexitedSuccessfully
, прекращение будет необходимо принудительно. Обратите внимание , чтоprocess.Kill()
выполняется асинхронно, реф замечания . Тем не менее, вызовprocess.WaitForExit()
сразу после этого снова откроет возможность тупиков. Таким образом, даже в случае принудительного завершения лучше разрешить очистку всех одноразовых изделий по окончанииusing
области, так как в любом случае выходные данные можно считать прерванными / поврежденными.try catch
Конструкция зарезервирован для исключительного случая (не каламбур) , где вы выровненыprocessTimeOutMilliseconds
с реальным временем , необходимым в процессе завершения. Другими словами, междуProcess.Exited
событием и таймером возникает состояние гонки . Возможность этого снова возрастает благодаря асинхронной природеprocess.Kill()
. Я сталкивался с этим один раз во время тестирования.Для полноты,
DisposeWith
метод расширения.источник
ExecuteScriptRx
ручкиhangs
отлично. К сожалению, зависания все еще случаются, но я просто добавил небольшую обертку над вашей,ExecuteScriptRx
которая работает,Retry
а затем она работает нормально. Причиной зависания MSBUILD может быть ответ @Clint. PS: этот код заставил меня чувствовать себя глупо <lol> Это первый раз, когда я вижуSystem.Reactive.Linq;
Для удобства читателей я собираюсь разделить это на 2 раздела
Раздел A: Проблема и как обращаться с подобными сценариями
Раздел B: Проблема отдыха и решения
Раздел А: Проблема
В вашем коде:
Process.WaitForExit(ProcessTimeOutMiliseconds);
С этим ты ждешьProcess
, чтобы тайм - аут или выход , который когда - либо происходит первым .OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
иerrorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
с этим вы ждетеOutputData
&ErrorData
потокового чтения, чтобы сигнализировать о его завершенииProcess.ExitCode == 0
Получает статус процесса при выходеРазличные настройки и их предостережения:
ObjectDisposedException()
Process.ExitCode
приводит к ошибкеSystem.InvalidOperationException: Process must exit before requested information can be determined
.Я тестировал этот сценарий более десятка раз и работает нормально, следующие параметры были использованы при тестировании
Обновленный код
РЕДАКТИРОВАТЬ:
После нескольких часов игр с MSBuild я наконец смог воспроизвести проблему на моей системе
Раздел B: Проблема отдыха и решения
Я смог решить эту проблему несколькими способами
Spawn MSBuild обрабатывается косвенно через CMD
Продолжайте использовать MSBuild, но убедитесь, что для nodeReuse установлено значение False
Даже если параллельная сборка не включена, вы все равно можете предотвратить зависание процесса
WaitForExit
, запустив сборку через CMD, и, следовательно, вы не создадите прямую зависимость от процесса сборки.Второй подход предпочтительнее, так как вы не хотите, чтобы вокруг было слишком много узлов MSBuild.
источник
"-nr:False","-m:3"
похоже, это исправило поведение зависания MSBuild, котороеRx solution
сделало весь процесс несколько надёжным (время покажет). Я хотел бы принять оба ответа или дать две наградыRx
подход в другом решении решить проблему без применения-nr:False" ,"-m:3"
. В моем понимании он обрабатывает неопределенное ожидание от взаимоблокировок и других вещей, которые я рассмотрел в разделе 1. И коренная причина в Разделе 2 - это то, что я считаю основной причиной проблемы, с которой вы столкнулись;) Я могу ошибаться, поэтому Я спросил, только время покажет ... Ура!Проблема в том, что при перенаправлении StandardOutput и / или StandardError внутренний буфер может заполниться.
Для решения вышеупомянутых проблем вы можете запустить процесс в отдельных потоках. Я не использую WaitForExit, я использую событие exited процесса, которое будет возвращать ExitCode процесса асинхронно, гарантируя его завершение.
Приведенный выше код проверен в бою и вызывает FFMPEG.exe с аргументами командной строки. Я конвертировал mp4 файлы в mp3 и записывал более 1000 видео одновременно, без сбоев. К сожалению, я не имею опыта работы с Power Shell, но надеюсь, что это поможет.
источник
BegingOutputReadline
а затем выполнитьReadToEndAsync
наStandardError
?Не уверен, что это ваша проблема, но, глядя на MSDN, вы видите некоторую странность с перегруженным WaitForExit при асинхронном перенаправлении вывода. В статье MSDN рекомендуется вызывать WaitForExit, который не принимает аргументов после вызова перегруженного метода.
Страница документов находится здесь. Соответствующий текст:
Модификация кода может выглядеть примерно так:
источник
process.WaitForExit()
как указано в комментариях к этому ответу .