Асинхронное чтение вывода cmd в .NET — зависание при входном запросе процесса

Я асинхронно читаю вывод из пакетного файла после его запуска с некоторыми параметрами. Если пакетный файл ожидает ввода - текст запроса ввода не перенаправляется - если процесс не завершен (что, очевидно, слишком поздно для ответа).

При выполнении в стандартном окне cmd запрос будет таким:

OpenEdge Release 10.2B07 as of Fri Sep  7 02:16:54 EDT 2012
testdb already exists.
Do you want to over write it? [y/n]:

Вывод при использовании перенаправления будет зависать без запуска события outputdatareceived, поэтому я не могу обработать входной запрос и ответить соответствующим образом. Консоль не читает последнюю строку (запрос на ввод):

OpenEdge Release 10.2B07 as of Fri Sep  7 02:16:54 EDT 2012
testdb already exists.

Код:

Private Sub someMethod()
    Dim process As New Process()
    process.StartInfo = New ProcessStartInfo("C:\OEV10\bin\_dbutil")
    process.StartInfo.WorkingDirectory = "C:\Temp\"
    process.StartInfo.Arguments = "prorest testdb C:\Temp\testdb.bck -verbose"
    process.EnableRaisingEvents = True

    With process.StartInfo
        .UseShellExecute = False
        .RedirectStandardError = True
        .RedirectStandardOutput = True
        .RedirectStandardInput = True
        .CreateNoWindow = False
        .StandardOutputEncoding = System.Text.Encoding.GetEncoding(Globalization.CultureInfo.CurrentUICulture.TextInfo.OEMCodePage)
        .StandardErrorEncoding = System.Text.Encoding.GetEncoding(Globalization.CultureInfo.CurrentUICulture.TextInfo.OEMCodePage)
    End With

    AddHandler process.Exited, AddressOf ProcessExited
    AddHandler process.OutputDataReceived, AddressOf Async_Data_Received2
    AddHandler process.ErrorDataReceived, AddressOf Async_Data_Received2

    process.Start()
    process.BeginOutputReadLine()
    process.BeginErrorReadLine()
End Sub

Private Sub Async_Data_Received2(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
    Console.WriteLine(e.Data)
End Sub

person madlan    schedule 21.11.2015    source источник
comment
Если бы это был UNIX, вы, вероятно, написали бы это как 'yes | прорест..."   -  person Tom Bascom    schedule 22.11.2015
comment
OutputDataReceived срабатывает при получении полной строки. Но, по-видимому, ваш командный файл не ставит символ новой строки в конце строки Do you want to over write it? [y/n]:.   -  person user4003407    schedule 22.11.2015
comment
Том: я делаю именно это для удаления (которое всегда будет запрашивать), однако для восстановления это больше для обработки ошибок, поскольку база данных не должна существовать, хотя я хотел бы, чтобы приложение безопасно реагировало, получая приглашение.   -  person madlan    schedule 22.11.2015
comment
PetSerAl - Вы правы - Есть ли способ получить строку без нового символа строки?   -  person madlan    schedule 22.11.2015
comment
@madlan Вы можете написать свою собственную процедуру чтения Steam, которая будет читать и сообщать о неполных строках. Я не знаю Visual Basic, поэтому я не могу вам помочь. Я могу написать что-нибудь на C#, но не уверен, что это поможет.   -  person user4003407    schedule 22.11.2015
comment
@PetSerAl - я тоже говорю на C#, так что это поможет.   -  person madlan    schedule 22.11.2015


Ответы (1)


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

public static async Task ReadTextReaderAsync(TextReader reader, IProgress<string> progress) {
    char[] buffer = new char[1024];
    for(;;) {
        int count = await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
        if(count==0) {
            break;
        }
        progress.Report(new string(buffer, 0, count));
    }
}

Эта реализация просто считывает строки из TextReader и сообщает о них через экземпляр IProgress<string>. Он не разбивает строки символами новой строки, а сохраняет их внутри строк. И тестовая программа:

public static void Main() {
    ProcessStartInfo psi = new ProcessStartInfo("Test.cmd") {
        UseShellExecute=false,
        RedirectStandardOutput=true,
        RedirectStandardError=true
    };
    Process p = Process.Start(psi);

    // Progress<T> implementation of IProgress<T> capture current SynchronizationContext,
    // so if you create Progress<T> instance in UI thread, then passed delegate
    // will be invoked in UI thread and you will be able to interact with UI elements.
    Progress<string> writeToConsole = new Progress<string>(Console.Write);

    Task stdout = ReadTextReaderAsync(p.StandardOutput, writeToConsole);
    Task stderr = ReadTextReaderAsync(p.StandardError, writeToConsole);

    // You possibly want asynchronous wait here, but for simplicity I will use synchronous wait.
    p.WaitForExit();
    stdout.Wait();
    stderr.Wait();
}
person user4003407    schedule 22.11.2015