Обновление потока пользовательского интерфейса (текстовое поле) через C # - PullRequest
5 голосов
/ 25 ноября 2011

Парень из бизнес-аналитики здесь с достаточным C # под моим поясом, чтобы быть опасным.

Я создал домашнее приложение winforms, которое по существу выполняет инструмент командной строки в цикле, чтобы «делать вещи». Сказанное может закончиться за считанные секунды или минуты. Обычно мне нужно будет запустить инструмент один раз для каждой строки в DataTable.

Мне нужно перенаправить вывод инструмента командной строки и отобразить его в «моем» приложении. Я пытаюсь сделать это через текстовое поле. У меня возникают проблемы с обновлением потока пользовательского интерфейса, которые я не могу исправить самостоятельно.

Чтобы запустить мой инструмент командной строки, я позаимствовал здесь код: Как проанализировать вывод командной строки из c #?

Вот мой эквивалент:

        private void btnImport_Click(object sender, EventArgs e)
        {
           txtOutput.Clear();
            ImportWorkbooks(dtable);

        }


        public void ImportWorkbooks(DataTable dt)
        {

            ProcessStartInfo cmdStartInfo = new ProcessStartInfo();
            cmdStartInfo.FileName = @"C:\Windows\System32\cmd.exe";
            cmdStartInfo.RedirectStandardOutput = true;
            cmdStartInfo.RedirectStandardError = true;
            cmdStartInfo.RedirectStandardInput = true;
            cmdStartInfo.UseShellExecute = false;
            cmdStartInfo.CreateNoWindow = false;

            Process cmdProcess = new Process();
            cmdProcess.StartInfo = cmdStartInfo;
            cmdProcess.ErrorDataReceived += cmd_Error;
            cmdProcess.OutputDataReceived += cmd_DataReceived;
            cmdProcess.EnableRaisingEvents = true;
            cmdProcess.Start();
            cmdProcess.BeginOutputReadLine();
            cmdProcess.BeginErrorReadLine();

            //Login
            cmdProcess.StandardInput.WriteLine(BuildLoginString(txtTabCmd.Text, txtImportUserName.Text, txtImportPassword.Text, txtImportToServer.Text)); 


            foreach (DataRow dr in dt.Rows)
            {
                   cmdProcess.StandardInput.WriteLine(CreateServerProjectsString(dr["Project"].ToString(), txtTabCmd.Text));

                //Import Workbook

                cmdProcess.StandardInput.WriteLine(BuildPublishString(txtTabCmd.Text, dr["Name"].ToString(), dr["UID"].ToString(),dr["Password"].ToString(), dr["Project"].ToString()));
            }
            cmdProcess.StandardInput.WriteLine("exit");   //Execute exit.
            cmdProcess.EnableRaisingEvents = false;
            cmdProcess.WaitForExit();
        }


private void cmd_DataReceived(object sender, DataReceivedEventArgs e)
        {
            //MessageBox.Show("Output from other process");
            try
            {
        // I want to update my textbox here, and then position the cursor 
        // at the bottom ala:

                StringBuilder sb = new StringBuilder(txtOutput.Text);
                sb.AppendLine(e.Data.ToString());
                txtOutput.Text = sb.ToString();
                this.txtOutput.SelectionStart = txtOutput.Text.Length;
                this.txtOutput.ScrollToCaret();


            }
            catch (Exception ex)
            {
                 Console.WriteLine("{0} Exception caught.", ex);

            }

        }

Ссылка на txtOuput.text, когда я создаю экземпляр своего StringBuilder в cmd_DataReceived (), аккуратно приводит к зависанию приложения: я предполагаю какую-то проблему с несколькими потоками.

Если я удалю ссылку на txtOuput.text в StringBuilder и продолжу отладку, я получу перекрестное нарушение здесь:

txtOutput.Text = sb.ToString();

Cross-thread operation not valid: Control 'txtOutput' accessed from a thread other than the thread it was created on.

Хорошо, не удивлен. Я предположил, что cmd_DataReceived выполняется в другом потоке, поскольку я выполняю его как результат выполнения каких-либо действий после Process.Start ()… и если я удаляю ВСЕ ссылки на txtOuput.Text в cmd_DataReceived () и просто выводю текст командной строки вывод на консоль через Console.Write (), все отлично работает.

Итак, я собираюсь попробовать стандартные методы для обновления моего TextBox в потоке пользовательского интерфейса, используя информацию в http://msdn.microsoft.com/en-us/library/ms171728.aspx

Я добавляю делегата и поток в свой класс:

delegate void SetTextCallback(string text);
// This thread is used to demonstrate both thread-safe and
// unsafe ways to call a Windows Forms control.
private Thread demoThread = null;

Я добавляю процедуру для обновления текстового поля:

 private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.txtOutput.InvokeRequired)
        {
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.txtOutput.Text = text;
        }
    }

Я добавляю другой процесс, который вызывает потокобезопасный:

 private void ThreadProcSafe()
    {
       // this.SetText(sb3.ToString());
        this.SetText("foo");

    }

... и, наконец, я вызываю этот беспорядок в cmd_DataПоступила так:

private void cmd_DataReceived(object sender, DataReceivedEventArgs e)
{
    //MessageBox.Show("Output from other process");
    try
    {

        sb3.AppendLine(e.Data.ToString());

        //this.backgroundWorker2.RunWorkerAsync();
        this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
        this.demoThread.Start();
        Console.WriteLine(e.Data.ToString());

    }
    catch (Exception ex)
    {
         Console.WriteLine("{0} Exception caught.", ex);


    }

}

… Когда я запускаю этот текст, текстовое поле остается мертвым, как рупор, и не обновляется. Окно моей консоли продолжает обновляться. Как вы можете видеть, я попытался немного упростить вещи, просто заставив текстовое поле отображать «foo» в сравнении с реальным выводом из инструмента - но без радости. Мой интерфейс мертв.

Так что дает? Не могу понять, что я делаю не так. Я вовсе не женат, чтобы отображать результаты в текстовом поле, кстати - мне просто нужно иметь возможность видеть, что происходит внутри приложения, и я предпочел бы не открывать другое окно для этого.

Большое спасибо.

Ответы [ 3 ]

4 голосов
/ 25 ноября 2011

Я думаю, что проблема в этой строке:

cmdProcess.WaitForExit(); 

Он находится в методе ImportWorkbooks, который вызывается из метода события Click btnImport. Таким образом, ваш поток пользовательского интерфейса блокируется до завершения фонового процесса.

3 голосов
/ 25 ноября 2011

Вы вызываете ImportWorkbooks из потока пользовательского интерфейса.Затем в этом методе вы вызываете "cmdProcess.WaitForExit ()".Таким образом, в основном вы блокируете поток пользовательского интерфейса, пока процесс не завершится.Запустите ImportWorkbooks из потока, и он должен сработать, или удалите WaitForExit и используйте вместо него событие «Exited».

0 голосов
/ 25 ноября 2011

одна из причин, по которой текстовое поле не обновляется, заключается в том, что вы не передаете строку в свой метод SetText.

Вам не нужно создавать тему. Ваша реализация SetText будет обрабатывать передачу вызова из рабочего потока (где вызывается cmd_DataReceived) в поток пользовательского интерфейса.

Вот что я предлагаю вам сделать:

private void cmd_DataReceived(object sender, DataReceivedEventArgs e)
{
    //MessageBox.Show("Output from other process");
    try
    {


        string str = e.Data.ToString();
        sb3.AppendLine(str);
        SetText(str); //or use sb3.ToString if you need the entire thing   

        Console.WriteLine(str);

    }
    catch (Exception ex)
    {
         Console.WriteLine("{0} Exception caught.", ex);


    }

}

Кроме того, вы блокируете поток пользовательского интерфейса, как упоминал @Fischermaen, когда вызываете WaitForExit, он вам не нужен.

Я бы также предложил запустить ImportWorkbooks в рабочем потоке следующим образом: (если вы сделаете это, вы можете оставить вызов WaitForExit)

private void btnImport_Click(object sender, EventArgs e)
{
     txtOutput.Clear();
     ThreadPool.QueueUserWorkItem(ImportBooksHelper, dtTable);
}

private ImportBooksHelper(object obj)
{
    DataTable dt = (DataTable)obj;
    ImportWorkbooks(dtable);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...