Текстовое поле не обновляется из другого потока, если не отображается MessageBox

#c# #multithreading #user-interface

#c# #многопоточность #пользовательский интерфейс

Вопрос:

Я пытаюсь выполнить программу командной строки и распечатать ее вывод в текстовое поле в режиме реального времени:

 private void btnExecute_Click(object sender, EventArgs e)
{
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.CreateNoWindow = true;
    startInfo.FileName = Application.StartupPath   "\Deps\ats.exe";
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardOutput = true;

    using (Process exeProcess = Process.Start(startInfo))
    {
        exeProcess.OutputDataReceived  = exeProcess_OutputDataReceived;
        exeProcess.BeginOutputReadLine();

        //MessageBox.Show("Hello"); //Notice this message box before calling WaitForExit                    
        exeProcess.WaitForExit(45000);
    }

    private void exeProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (txtOutput.InvokeRequired)
        {
            txtOutput.Invoke(new MethodInvoker(delegate { txtOutput.Text  = Environment.NewLine   e.Data; }));
        }
    }
}
  

Код выполняется без ошибок, но ничего не выводит в txtOutput
Однако, если я раскомментирую окно сообщения, отобразится MessageBox и txtOutput будет обновляться в режиме реального времени
Теперь, если я нажму «ОК», чтобы закрыть MessageBox, txtOutput снова прекратит обновление!!

Что именно здесь происходит? Почему текстовое поле обновляется только тогда, когда я показываю MessageBox?

Комментарии:

1. Я подозреваю, что на самом деле вы не получаете выходные данные из своего консольного приложения так, как вы думаете (мое перенаправление консоли немного ржавое). Поместите некоторый отладочный код в свой делегат для вывода e.Data в окно отладки, чтобы убедиться, что вы действительно получаете некоторый вывод.

2. @CodingGorilla, я знаю, что получаю вывод из консольного приложения, потому что пока отображается MessageBox, в текстовом поле отображается вывод, но как только я закрываю окно сообщения, вывод перестает обновляться

Ответ №1:

Хорошо, итак, проблема, с которой вы столкнулись, вероятно, заключается в том, что exeProcess.WaitForExit(45000); блокирует поток пользовательского интерфейса. Таким образом, вызов отправляет txtOutput.Invoke сообщение в очередь сообщений window, которая обрабатывается потоком пользовательского интерфейса. Поскольку этот поток пользовательского интерфейса заблокирован состоянием ожидания, он не может обрабатывать эти сообщения текстового поля.

Итак, что вам нужно сделать, это дождаться завершения процесса, не блокируя поток пользовательского интерфейса. Есть несколько способов сделать это, но одним из способов было бы использовать Exited событие процесса с обратным вызовом. Я не вижу здесь достаточно вашего кода, чтобы привести полный пример, но вы хотели бы переписать что-то вроде этого:

 private Process _exeProcess;

private void btnExecute_Click(object sender, EventArgs e)
{
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.CreateNoWindow = true;
    startInfo.FileName = Application.StartupPath   "\Deps\ats.exe";
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardOutput = true;

    _exeProcess = Process.Start(startInfo);
    _exeProcess.OutputDataReceived  = exeProcess_OutputDataReceived;
    _exeProcess.BeginOutputReadLine();    
    _exeProcess.Exited  = ContinueOnYourWay;
}

private void ContinueOnYourWay(object sender, EventArgs e) 
{
    // Clean up
    _exeProcess.Dispose();
    _exeProcess = null;

    // More code here
}

private void exeProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    if (txtOutput.InvokeRequired)
    {
        txtOutput.Invoke(new MethodInvoker(delegate { txtOutput.Text  = Environment.NewLine   e.Data; }));
    }
 }
  

Комментарии:

1. Да, это работает, мне придется немного изменить остальную часть моего кода, я думаю, я также могу подождать, например: while (!exeProcess. HasExited) { //Поток. Спящий режим (500); Приложение. DoEvents(); }

2. Как общее эмпирическое правило, обычно не рекомендуется помещать какое-либо ожидание в обработчик событий пользовательского интерфейса (т. Е. Поток пользовательского интерфейса). Это почти всегда приводит к зависанию вашего пользовательского интерфейса и перестает отвечать на запросы, а в более поздних версиях Windows вызывает диалоговое окно «Эта программа не отвечает», в котором запрашивается у пользователя, хотят ли они его отключить.

3. Только что увидел редактирование вашего предыдущего комментария. Application.DoEvents() Работает, в основном WndProc это позволяет ему обрабатывать несколько событий. Лично я бы не стал этого делать, но это должно сработать.

4. Спасибо за объяснение, я думаю, что завершенное событие будет хорошим решением