@Quttar72
Изучаю asp.net core mvc

Почему при чтении из потока read блокируется, даже если данные есть?

Мне нужно читать, какие данные запущенный мною процесс пишет в выходной поток. Для этого при запуске процесса, в ProcessStartInfo я устанавливаю перенаправление выходного потока (к слову всех потоков, но это не так важно).

У объекта process.StandardOutput имеются два метода Read и ReadBlock, второй как я выяснил производит блокировку, но тем не менее в документации про нее ничего не написано. Первый же метод, это то что мне нужно, но я не понимаю как он работает.

Я его использую следующим образом:
int next = 0;
do
{
    int realReaded = process.StandardOutput.Read(buffer, 0, buffer.Length);
    Console.Write(buffer, 0, realReaded);
    next = process.StandardOutput.Peek();
} while (next != -1);


Используемая перегрузка Read должна возвращать количество реально прочитанных символов, либо ноль, если их нет. (то есть по идее даже Peek не нужен). Проблема в том, что когда данных нет - он производит блокировку. Окей, для этого казалось бы можно использовать Peek. Если данных больше чем размер буфера, то он заполнится и метод выдаст число символов равное размеру массива. Да! Тут все работает. Но, если количество символов меньше чем размер буфера, то он должен заполнить его не полностью, а вернуть реально заполненное число символов? Если конкретнее говорить, то вот более уточненная постановка моего вопроса.

Из моих наблюдений оно работает - по разному. Например, если у меня буфер 256 символов - в него все помещается и я за раз получаю все символы, которые процесс должен записать при запуске.

Но вот если буфер в 10 символов, то все работает ровно до последней порции данных - в нее вроде бы и должны входить символы, но метод блокируется и я не могу их прочитать. Причем число символов не кратно 10, это я проверил с помощью дебаггера посмотрев сколько символов считывается при буфере в 256 символов. С буфером в 50 символов тоже самое.

Можно было бы в теории использовать буфер в 256 символов, но потом и с этим буфером продолжается проблема - он блокируется, если данных в потоке меньше чем размер буфера.

Согласно документации этот метод не должен блокировать процесс в ожидании данных. Мне кажется я не лезу в какие-то дебри, это должны быть весьма часто-используемые механизмы, а моему некоторому возмущению по поводу работы этого метода, есть вполне логичное объяснение. Подскажите, пожалуйста, где я ошибся в использовании этого метода или может ссылку на статью, где описывается как правильно организовывать взаимодействие с запущенным процессом по стандартным потокам.
  • Вопрос задан
  • 104 просмотра
Пригласить эксперта
Ответы на вопрос 1
@rPman
Тебе нужно асинхронное чтение потоков процесса, это можно сделать, добавив на колбек свой метод process.OutputDataReceived и ErrorDataReceived соответственно, а после process.Start() сразу запускаешь чтение потоков process.BeginOutputReadLine и BeginErrorReadLine

Вот дока с примером
--------------upd---------------

Для чтения не по строкам, пользуйся асинхронным методом у StreamReader - ReadBlockAsync

Чтобы можно было брать потоки process.StandardOutput и process.StandardError, у process.SystemInfo нужно установить RedirectStandardOutput = true и RedirectStandardError = true соответственно.

Осторожно, ReadBlockAsync может вернуть блок меньшего размера

Пример кода
using System;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;

namespace ProcessStdoutTaskTest
{
    class MainClass
    {
        static char[] bufOut = new char[5];
        static char[] bufErr = new char[5];
        async static void ReadAsyncStream(StreamReader sr, char[] buf)
        {
            int size;
            while ((size = await sr.ReadBlockAsync(buf, 0, buf.Length)) != 0)
            {
                Console.WriteLine((buf==bufOut?"out":"err")+" '" + new string(buf, 0, size) + "'") ;
            }
        }
        public static void Main(string[] args)
        {
            using (Process process = new Process())
            {
                process.StartInfo.FileName = "/bin/bash";
                process.StartInfo.Arguments = "-c \"printf 'abc';printf 'def' 1>&2;printf 'ghi';printf 'j' 1>&2;\"";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.Start();

                ReadAsyncStream(process.StandardOutput, bufOut);
                ReadAsyncStream(process.StandardError, bufErr);

                Console.Write("Wait to process exit ");
                process.WaitForExit();
                Console.WriteLine("ok");
            }

            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }
    }
}

Будет вывод:
Wait to process exit ok
Press enter to exit.
out 'abcgh'
out 'i'
err 'defj'

Кстати поведение объединения отдельных строк в потоках из разных кусочков не гарантируется, как я понимаю это зависит от того как использует flush на stdout запускаемый процесс, в моем примере это bash и он явно буферизирует вывод
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы