Зависание процесса при запуске с.NET Process.Start - что не так?
Я написал быструю и грязную оболочку вокруг svn.exe, чтобы извлечь некоторый контент и что-то с ним сделать, но для определенных входных данных он иногда и воспроизводимо зависает и не завершается. Например, один вызов в список svn:
svn list "http://myserver:84/svn/Documents/Instruments/" --xml --no-auth-cache --username myuser --password mypassword
Эта командная строка работает нормально, когда я просто делаю это из командной оболочки, но она зависает в моем приложении. Мой код C# для запуска это:
string cmd = "svn.exe";
string arguments = "list \"http://myserver:84/svn/Documents/Instruments/\" --xml --no-auth-cache --username myuser --password mypassword";
int ms = 5000;
ProcessStartInfo psi = new ProcessStartInfo(cmd);
psi.Arguments = arguments;
psi.RedirectStandardOutput = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
StreamReader output = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8);
proc.WaitForExit(ms);
if (proc.HasExited)
{
return output.ReadToEnd();
}
Это занимает полные 5000 мс и никогда не заканчивается. Продление времени не помогает. В отдельной командной строке он запускается мгновенно, поэтому я уверен, что он не связан с недостаточным временем ожидания. Однако для других входов это работает нормально.
Я также попытался запустить отдельный cmd.exe здесь (где exe - svn.exe, а args - исходная строка arg), но зависание все же произошло:
string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";
Что я мог здесь напортачить, и как я могу отлаживать этот внешний процесс?
РЕДАКТИРОВАТЬ:
Я только сейчас приступаю к решению этого. Большое спасибо Джону Скиту за его предложение, которое действительно прекрасно работает. У меня есть еще один вопрос о моем методе обработки этого, так как я многопоточный новичок. Я хотел бы получить предложения по улучшению любых явных недостатков или чего-то еще глупого. Я закончил тем, что создал небольшой класс, который содержит поток stdout, StringBuilder для хранения вывода и флаг, чтобы сообщить, когда он закончится. Затем я использовал ThreadPool.QueueUserWorkItem и передал экземпляр моего класса:
ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);
proc.WaitForExit(ms);
if (proc.HasExited)
{
bufferHandler.Stop();
return bufferHandler.ReadToEnd();
}
... а также...
private class ProcessBufferHandler
{
public Stream stream;
public StringBuilder sb;
public Encoding encoding;
public State state;
public enum State
{
Running,
Stopped
}
public ProcessBufferHandler(Stream stream, Encoding encoding)
{
this.stream = stream;
this.sb = new StringBuilder();
this.encoding = encoding;
state = State.Running;
}
public void ProcessBuffer()
{
sb.Append(new StreamReader(stream, encoding).ReadToEnd());
}
public string ReadToEnd()
{
return sb.ToString();
}
public void Stop()
{
state = State.Stopped;
}
}
Кажется, это работает, но я сомневаюсь, что это лучший способ. Это разумно? И что я могу сделать, чтобы улучшить это?
6 ответов
Одна стандартная проблема: процесс может ждать, пока вы прочитаете его вывод. Создайте отдельный поток для чтения из его стандартного вывода, пока вы ожидаете его выхода. Это немного больно, но это может быть проблемой.
Джон Скит прав на деньги!
Если вы не против опроса после запуска команды svn, попробуйте это:
Process command = new Process();
command.EnableRaisingEvents = false;
command.StartInfo.FileName = "svn.exe";
command.StartInfo.Arguments = "your svn arguments here";
command.StartInfo.UseShellExecute = false;
command.StartInfo.RedirectStandardOutput = true;
command.Start();
while (!command.StandardOutput.EndOfStream)
{
Console.WriteLine(command.StandardOutput.ReadLine());
}
Мне пришлось оставить exe-файл на компьютере клиента и использовать Process.Start для его запуска.
Вызывающее приложение зависало - проблема заключалась в том, что их компьютер предполагал, что исполняемый файл опасен и не позволяет другим приложениям запускать его.
Щелкните правой кнопкой мыши на exe и перейдите в свойства. Нажмите "Разблокировать" внизу рядом с предупреждением безопасности.
Основываясь на ответе Джона Скита, вот как я это делаю в наши дни (2021 г.) .NET 5
var process = Process.Start(processStartInfo);
var stdErr = process.StandardError;
var stdOut = process.StandardOutput;
var resultAwaiter = stdOut.ReadToEndAsync();
var errResultAwaiter = stdErr.ReadToEndAsync();
await process.WaitForExitAsync();
Task.WaitAll(resultAwaiter, errResultAwaiter);
var result = resultAwaiter.Result;
var errResult = errResultAwaiter.Result;
Обратите внимание, что вы не можете дождаться стандартного вывода перед ошибкой, потому что ожидание будет зависать в случае, если стандартный буфер ошибок заполнится первым (то же самое, если попытаться сделать наоборот).
Единственный способ - начать читать их асинхронно, дождаться завершения процесса, а затем завершить ожидание, используя
Task.WaitAll
Я знаю, что это старый пост, но, возможно, это кому-то поможет. Я использовал это для выполнения некоторых команд CLI AWS (Amazon Web Services), используя задачи.Net TPL.
Я сделал что-то подобное в моей команде, которая выполняется в рамках.Net TPL Task, которая создается в моем фоновом работнике WinForm bgwRun_DoWork
метод, который держит цикл с while(!bgwRun.CancellationPending)
, Он содержит чтение стандартного вывода из процесса через новый поток с использованием класса.Net ThreadPool.
private void bgwRun_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgwRun.CancellationPending)
{
//build TPL Tasks
var tasks = new List<Task>();
//work to add tasks here
tasks.Add(new Task(()=>{
//build .Net ProcessInfo, Process and start Process here
ThreadPool.QueueUserWorkItem(state =>
{
while (!process.StandardOutput.EndOfStream)
{
var output = process.StandardOutput.ReadLine();
if (!string.IsNullOrEmpty(output))
{
bgwRun_ProgressChanged(this, new ProgressChangedEventArgs(0, new ExecutionInfo
{
Type = "ExecutionInfo",
Text = output,
Configuration = s3SyncConfiguration
}));
}
if (cancellationToken.GetValueOrDefault().IsCancellationRequested)
{
break;
}
}
});
});//work Task
//loop through and start tasks here and handle completed tasks
} //end while
}
Я знаю, что мои репозитории SVN иногда могут работать медленно, поэтому, может быть, 5 секунд недостаточно? Скопировали ли вы строку, которую вы передаете процессу, из точки останова, так что вы уверены, что она вас ни о чем не спрашивает?