public record ProcessRunnerResult(int ExitCode, string? StdErr, string? StdOut, ExceptionDispatchInfo? ExceptionDispatchInfo = null);
public class ProcessRunnerException(string executablePath, string? args, int exitCode, string? stdErr = null, string? stdOut = null, Exception? innerException = null)
: Exception($"Running > failed with exit code {exitCode}", innerException)
{
public string? StdOut { get; } = stdOut;
public string? StdErr { get; } = stdErr;
public int ExitCode { get; } = exitCode;
}
public class ProcessRunner
{
public enum Mode
{
NeverThrow,
RethrowReturnExitCode,
RethrowAndIfNonZeroExitCode,
RethrowIfNotCancelledReturnExitCode
}
public async Task Run(string executablePath,
Mode mode,
string? args = null,
string? workingDirectory = null,
Action? beforeInvoke = null,
Action? afterInvoke = null,
Action? onCancelInvoke = null,
Action? notifyStdOutLine = null,
Action? notifyStdErrLine = null,
CancellationToken cancellationToken = default)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = executablePath,
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
UseShellExecute = false,
WorkingDirectory = workingDirectory
}
};
StringBuilder stdOutBuilder = new();
StringBuilder stdErrBuilder = new();
try
{
beforeInvoke?.Invoke(process.StartInfo);
process.Start();
afterInvoke?.Invoke(process);
Task[] tasks = [
ReadStreamAsync(notifyStdOutLine, stdOutBuilder, process.StandardOutput, cancellationToken),
ReadStreamAsync(notifyStdErrLine, stdErrBuilder, process.StandardError, cancellationToken),
];
using (cancellationToken.Register(() =>
{
onCancelInvoke?.Invoke(process);
}))
{
await Task.WhenAll(tasks).ConfigureAwait(false);
// Can stdout and stderr be closed by the child process before exiting? Who knows, but let us make sure the process is done.
await process.WaitForExitAsync(cancellationToken);
}
}
catch (OperationCanceledException) when (mode == Mode.RethrowIfNotCancelledReturnExitCode)
{
return new ProcessRunnerResult(-1, stdErrBuilder.ToString(), stdOutBuilder.ToString());
}
catch (Exception ex) when (mode == Mode.NeverThrow)
{
return new ProcessRunnerResult(-1, null, null, ExceptionDispatchInfo.Capture(ex));
}
catch (Exception ex)
{
throw new ProcessRunnerException(executablePath, args, -1, stdErrBuilder.ToString(), stdOutBuilder.ToString(), ex);
}
if (mode == Mode.RethrowAndIfNonZeroExitCode && process.ExitCode != 0)
{
throw new ProcessRunnerException(executablePath, args, process.ExitCode, stdErrBuilder.ToString(), stdOutBuilder.ToString());
}
return new ProcessRunnerResult(process.ExitCode, stdErrBuilder.ToString(), stdOutBuilder.ToString());
}
private static async Task ReadStreamAsync(Action? notifyLine, StringBuilder builder, StreamReader reader, CancellationToken cancellationToken)
{
string? line;
while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) != null)
{
builder.AppendLine(line);
notifyLine?.Invoke(line);
}
}
}
Он не должен блокироваться из-за перенаправления stdout и stderr, поскольку он читает из обоих потоков одновременно, никогда не блокируя ни один из них. Тем не менее, на некоторых машинах (но не на моей) периодически возникает взаимоблокировка.
Итак, я подумал: может быть, асинхронный ввод-вывод здесь неуместен, и мне следует использовать исходный подход, основанный на событиях? Итак, я изменил код для использования событий:
Но этот код также время от времени блокируется. Как и в случае с асинхронным вводом-выводом, это происходит не каждый раз, а периодически.
Так что ничего не изменилось. Это означает, что я делаю что-то не так с обеими реализациями, но не могу понять, что именно.
Где ошибка? EDIT 1
Обратные вызовы Action
кажутся проблематичными. Однако взаимоблокировки, с которыми мы сталкиваемся, возникают в коде, который вообще не передает эти обратные вызовы. И код, который передает эти обратные вызовы:
(process) => consoleOut.MarkupLine("[green]Command execution started. Press [[Esc]] to stop the command execution.[/]"),
(process) =>
{
if (!process.HasExited)
{
process.Kill();
}
},
РЕДАКТИРОВАНИЕ 2
Я изменил код, добавив ключевое слово using перед процессом > объявление объекта. Таким образом, объект теперь правильно расположен. Однако как асинхронный ввод-вывод, так и версии, основанные на событиях, по-прежнему время от времени блокируются в одном и том же месте.
Я хотел бы подчеркнуть, что это расположение НЕ Strong> передать обратные вызовы, принимающие аргумент Process — afterInvoke и onCancelInvoke.
У меня есть класс, который я использую для создания дочерних процессов. Вот его реализация: [code]public record ProcessRunnerResult(int ExitCode, string? StdErr, string? StdOut, ExceptionDispatchInfo? ExceptionDispatchInfo = null);
public class ProcessRunnerException(string executablePath, string? args, int exitCode, string? stdErr = null, string? stdOut = null, Exception? innerException = null) : Exception($"Running > failed with exit code {exitCode}", innerException) { public string? StdOut { get; } = stdOut; public string? StdErr { get; } = stdErr; public int ExitCode { get; } = exitCode; }
public class ProcessRunner { public enum Mode { NeverThrow, RethrowReturnExitCode, RethrowAndIfNonZeroExitCode, RethrowIfNotCancelledReturnExitCode }
using (cancellationToken.Register(() => { onCancelInvoke?.Invoke(process); })) { await Task.WhenAll(tasks).ConfigureAwait(false);
// Can stdout and stderr be closed by the child process before exiting? Who knows, but let us make sure the process is done. await process.WaitForExitAsync(cancellationToken); } } catch (OperationCanceledException) when (mode == Mode.RethrowIfNotCancelledReturnExitCode) { return new ProcessRunnerResult(-1, stdErrBuilder.ToString(), stdOutBuilder.ToString()); } catch (Exception ex) when (mode == Mode.NeverThrow) { return new ProcessRunnerResult(-1, null, null, ExceptionDispatchInfo.Capture(ex)); } catch (Exception ex) { throw new ProcessRunnerException(executablePath, args, -1, stdErrBuilder.ToString(), stdOutBuilder.ToString(), ex); }
if (mode == Mode.RethrowAndIfNonZeroExitCode && process.ExitCode != 0) { throw new ProcessRunnerException(executablePath, args, process.ExitCode, stdErrBuilder.ToString(), stdOutBuilder.ToString()); }
return new ProcessRunnerResult(process.ExitCode, stdErrBuilder.ToString(), stdOutBuilder.ToString()); }
private static async Task ReadStreamAsync(Action? notifyLine, StringBuilder builder, StreamReader reader, CancellationToken cancellationToken) { string? line; while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) != null) { builder.AppendLine(line); notifyLine?.Invoke(line); } } } [/code] Он не должен блокироваться из-за перенаправления stdout и stderr, поскольку он читает из обоих потоков одновременно, никогда не блокируя ни один из них. Тем не менее, на некоторых машинах (но не на моей) периодически возникает взаимоблокировка. Итак, я подумал: может быть, асинхронный ввод-вывод здесь неуместен, и мне следует использовать исходный подход, основанный на событиях? Итак, я изменил код для использования событий: [code]public class ProcessRunner { ... public async Task Run(string executablePath, Mode mode, string? args = null, string? workingDirectory = null, Action? beforeInvoke = null, Action? afterInvoke = null, Action? onCancelInvoke = null, Action? notifyStdOutLine = null, Action? notifyStdErrLine = null, CancellationToken cancellationToken = default) { using var process = new Process { StartInfo = new ProcessStartInfo { FileName = executablePath, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, WorkingDirectory = workingDirectory } };
using (cancellationToken.Register(() => { onCancelInvoke?.Invoke(process); })) { await process.WaitForExitAsync(cancellationToken); } } catch (OperationCanceledException) when (mode == Mode.RethrowIfNotCancelledReturnExitCode) { return new ProcessRunnerResult(-1, sbStdErr.ToString(), sbStdOut.ToString()); } catch (Exception ex) when (mode == Mode.NeverThrow) { return new ProcessRunnerResult(-1, sbStdErr.ToString(), sbStdOut.ToString(), ExceptionDispatchInfo.Capture(ex)); } catch (Exception ex) { throw new ProcessRunnerException(executablePath, args, -1, sbStdErr.ToString(), sbStdOut.ToString(), ex); }
if (mode == Mode.RethrowAndIfNonZeroExitCode && process.ExitCode != 0) { throw new ProcessRunnerException(executablePath, args, process.ExitCode, sbStdErr.ToString(), sbStdOut.ToString()); }
return new ProcessRunnerResult(process.ExitCode, sbStdErr.ToString(), sbStdOut.ToString()); }
private static void Process_OutputDataReceived(string? data, Action? notifyLine, StringBuilder sb) { if (data == null || data.Length == 0) { return; } sb.AppendLine(data); notifyLine?.Invoke(data); } } [/code] Но этот код также время от времени блокируется. Как и в случае с асинхронным вводом-выводом, это происходит не каждый раз, а периодически. Так что ничего не изменилось. Это означает, что я делаю что-то не так с обеими реализациями, но не могу понять, что именно. Где ошибка? [b]EDIT 1[/b] Обратные вызовы Action кажутся проблематичными. Однако взаимоблокировки, с которыми мы сталкиваемся, возникают в коде, который вообще не передает эти обратные вызовы. И код, который передает эти обратные вызовы: [code](process) => consoleOut.MarkupLine("[green]Command execution started. Press [[Esc]] to stop the command execution.[/]"), (process) => { if (!process.HasExited) { process.Kill(); } }, [/code] [b]РЕДАКТИРОВАНИЕ 2[/b] Я изменил код, добавив ключевое слово using перед процессом > объявление объекта. Таким образом, объект теперь правильно расположен. Однако как асинхронный ввод-вывод, так и версии, основанные на событиях, по-прежнему время от времени блокируются в одном и том же месте. Я хотел бы подчеркнуть, что это расположение НЕ Strong> передать обратные вызовы, принимающие аргумент Process — afterInvoke и onCancelInvoke.
Следующий код был рекомендован для C# — почтальон HttpClient, но он выдает ошибку (в почтальоне это работает).
Ниже приведен код:
var client = new System.Net.Http.HttpClient();
var request = new...
Моя проблема в том, что в моей очистке есть исправление предупреждений/ошибок из editorconfig. Но он также автоматически исправляет CS0535. Но я не хочу, чтобы это произошло. Как я могу скрыть сообщение об ошибке и получить исправление в виде...
Я не могу понять, почему я не могу нормально общаться с дочерним процессом.
У меня есть программа на C и Python. Python необходимо запустить программу C, а затем захватить выходные данные C неблокирующим способом. В этом примере программа на C будет...
Я использую создание дочернего процесса с перенаправленным входом и выводом как есть, но он застрял в функции WriteTopipe почти всегда с достаточно большими файлами (200 миб). Я пробовал много разных вещей, но не смог найти причину.