TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3194 1.88K
День 2666. #ЧтоНовенького #NET11
Улучшения API процессов в .NET 11. Продолжение
Обзор

Перехват выходных данных процесса без взаимоблокировок

Почему захват выходных данных процесса может приводить к зависанию
При перенаправлении стандартного вывода (stdout) и ошибок (stderr) процесса возможны взаимоблокировки. Понимание причин этого крайне важно для понимания сути новинок. Создадим приложение, которое пытается прочитать все выходные данные и ошибки процесса.

Прежде всего, нужно перенаправить stdout и stderr, чтобы иметь возможность их прочитать. Это делается путём установки свойств RedirectStandardOutput и RedirectStandardError объекта ProcessStartInfo в true. Перед запуском процесса создаются два выделенных канала (для вывода и для ошибок). Дочерний процесс записывает данные в свои stdout и stderr, как обычно, но вместо вывода в консоль данные записываются в каналы.
ProcessStartInfo startInfo = new("dotnet", "--help")
{
RedirectStandardOutput = true,
RedirectStandardError = true
};

using Process p = new() { StartInfo = startInfo };

Каналы имеют ограниченный размер буфера (обычно 4КБ в Windows и 64КБ в Unix). Когда производитель (в нашем случае, дочерний процесс) записывает данные в канал, они сохраняются в буфере до тех пор, пока их не прочтёт потребитель (родительский процесс). Если производитель записывает больше данных, чем размер буфера, а потребитель одновременно не читает из канала, производитель будет заблокирован на операции записи, ожидая, пока потребитель прочитает из канала и освободит место в буфере.
Если потребитель ожидает завершения работы производителя (например, вызывая WaitForExit) без чтения из канала, он будет заблокирован, как только производитель заполнит буфер:
p.Start();
p.WaitForExit();

var output = p.StandardOutput.ReadToEnd();
var error = p.StandardError.ReadToEnd();

Перемещение process.WaitForExit(); в конец тоже не помогает. ReadToEnd — блокирующий вызов: он читает до тех пор, пока поток не достигнет конца файла (EOF), что происходит только тогда, когда дочерний процесс закрывает свою часть канала (обычно при завершении). Поэтому в приведённом выше коде мы сначала блокируемся на stdout до завершения дочернего процесса, и только затем начинаем читать stderr. Если дочерний процесс записывает в stderr больше, чем может вместить буфер канала, он блокируется на своей записи — и мы застреваем во взаимоблокировке.

Причина в том, что мы читаем два потока последовательно, а не одновременно. Чтобы избежать этого, нужно одновременно очищать и стандартный вывод, и ошибку. До сих пор у нас было два варианта:
1. Использовать асинхронные операции чтения для StandardOutput и StandardError
p.Start();

var outTask = p.StandardOutput.ReadToEndAsync();
var errTask = process.StandardError.ReadToEndAsync();

await Task.WhenAll(outTask, errTask, p.WaitForExitAsync());

var output = await outTask;
var error = await errTask;


2. Использовать события OutputDataReceived и ErrorDataReceived
События возникают при записи строки в stdout и stderr соответственно:
StringBuilder stdOut = new(), stdErr = new();

p.OutputDataReceived += (sender, e) => stdOut.AppendLine(e.Data);
p.ErrorDataReceived += (sender, e) => stdErr.AppendLine(e.Data);

p.Start();

p.BeginOutputReadLine();
p.BeginErrorReadLine();

p.WaitForExit();

Существующие API не оптимальны с точки зрения простоты и производительности.

Продолжение следует…

Источник:
https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/
  • 👍 9
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →