Тупик с перенаправлением вывода дочернего процесса, несмотря на предложенный шаблонC#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Тупик с перенаправлением вывода дочернего процесса, несмотря на предложенный шаблон

Сообщение Anonymous »

У меня есть класс, который я использую для создания дочерних процессов. Вот его реализация:

Код: Выделить всё

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, поскольку он читает из обоих потоков одновременно, никогда не блокируя ни один из них. Тем не менее, на некоторых машинах (но не на моей) периодически возникает взаимоблокировка.
Итак, я подумал: может быть, асинхронный ввод-вывод здесь неуместен, и мне следует использовать исходный подход, основанный на событиях? Итак, я изменил код для использования событий:

Код: Выделить всё

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
}
};

StringBuilder sbStdOut = new();
StringBuilder sbStdErr = new();
try
{
beforeInvoke?.Invoke(process.StartInfo);

process.OutputDataReceived += (_, args) => Process_OutputDataReceived(args.Data, notifyStdOutLine, sbStdOut);
process.ErrorDataReceived += (_, args) => Process_OutputDataReceived(args.Data, notifyStdErrLine, sbStdErr);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();

afterInvoke?.Invoke(process);

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);
}
}
Но этот код также время от времени блокируется. Как и в случае с асинхронным вводом-выводом, это происходит не каждый раз, а периодически.
Так что ничего не изменилось. Это означает, что я делаю что-то не так с обеими реализациями, но не могу понять, что именно.
Где ошибка?
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.

Подробнее здесь: https://stackoverflow.com/questions/793 ... -suggested
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • С# - HttpClient, предложенный почтальоном, не работает
    Anonymous » » в форуме C#
    0 Ответы
    20 Просмотры
    Последнее сообщение Anonymous
  • Могу ли я установить уровень серьезности CS0535 в C# на предложенный?
    Anonymous » » в форуме C#
    0 Ответы
    8 Просмотры
    Последнее сообщение Anonymous
  • JAVA, найдите идентификатор дочернего процесса по идентификатору родительского процесса
    Anonymous » » в форуме JAVA
    0 Ответы
    33 Просмотры
    Последнее сообщение Anonymous
  • Чтение вывода неблокирующего дочернего процесса
    Anonymous » » в форуме Python
    0 Ответы
    13 Просмотры
    Последнее сообщение Anonymous
  • Создание дочернего процесса с перенаправленным примером ввода и вывода Get заблокирован
    Anonymous » » в форуме C++
    0 Ответы
    5 Просмотры
    Последнее сообщение Anonymous

Вернуться в «C#»