商业智能人员在这里有足够的C#是危险的。
我已经构建了一个自制的winforms应用程序,它基本上在循环中执行命令行工具来“执行东西”。所述东西可以在几秒或几分钟内完成。通常,我需要为位于DataTable中的每一行执行一次该工具。
我需要重定向命令行工具的输出并将其显示在“我的”应用中。我试图通过文本框这样做。我正在遇到有关更新UI线程的问题,而我自己无法理解这些问题。
要执行我的命令行工具,我从这里借用了代码:How to parse command line output from 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);
}
}
当我在cmd_DataReceived()中实例化我的StringBuilder时,引用txtOuput.text整齐地导致应用程序挂起:我猜是某种跨线程问题。
如果我在StringBuilder中删除对txtOuput.text的引用并继续调试,我会在这里遇到跨线程违规:
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()之后执行操作的结果...并且如果我在cmd_DataReceived()中删除对txtOuput.Text的所有引用并且只是转储命令行文本通过Console.Write()输出到控制台,一切正常。
所以,接下来我将尝试使用http://msdn.microsoft.com/en-us/library/ms171728.aspx
中的信息在UI线程上更新我的TextBox的标准技术我向我的班级添加了一个委托和线程:
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;
}
}
我添加另一个调用线程安全的proc:
private void ThreadProcSafe()
{
// this.SetText(sb3.ToString());
this.SetText("foo");
}
...最后我把这个混乱称为cmd_DataReceived,如下所示:
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”与工具的实际输出来简化一些事情 - 但没有快乐。我的用户界面已经死了。
那是什么给出的?无法弄清楚我做错了什么。我完全不想在文本框中显示结果,顺便说一句 - 我只需要能够看到应用程序内部的内容,我不想弹出另一个窗口来执行此操作。
非常感谢。
答案 0 :(得分:4)
我认为问题出在这一行:
cmdProcess.WaitForExit();
在方法ImportWorkbooks
中,它是从btnImport
的Click事件方法调用的。因此,您的UI线程将被阻止,直到后台进程完成。
答案 1 :(得分:3)
您正在从UI线程调用ImportWorkbooks。然后,在此方法中,您将调用“cmdProcess.WaitForExit()”。所以基本上你是阻止UI线程,直到进程完成执行。从一个线程执行ImportWorkbooks它应该工作,或者删除WaitForExit并使用进程''Exited'事件。
答案 2 :(得分:0)
文本框未更新的原因之一是因为您没有将字符串传递给SetText方法。
您无需创建线程。 您的SetText实现将处理从工作线程(调用cmd_DataReceived)到UI线程的调用。
我建议你这样做:
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);
}
}
此外,当您调用WaitForExit时,您正在阻止UI线程,如@Fischermaen,您不需要它。
我还建议您在工作线程上运行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);
}