Парень из бизнес-аналитики здесь с достаточным 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» в сравнении с реальным выводом из инструмента - но без радости. Мой интерфейс мертв.
Так что дает? Не могу понять, что я делаю не так. Я вовсе не женат, чтобы отображать результаты в текстовом поле, кстати - мне просто нужно иметь возможность видеть, что происходит внутри приложения, и я предпочел бы не открывать другое окно для этого.
Большое спасибо.