简体   繁体   English

通过C#更新UI线程(文本框)

[英]Updating UI thread (textbox) via C#

Business Intelligence guy here with enough C# under my belt to be dangerous. 商业智能人员在这里有足够的C#在我的腰带下是危险的。

I've built a homebrew winforms application that essentially executes a command-line tool in a loop to “do stuff”. 我已经构建了一个自制的winforms应用程序,它基本上在循环中执行命令行工具来“执行东西”。 Said stuff may complete in seconds or minutes. 所述东西可以在几秒或几分钟内完成。 Normally I will need to execute the tool once for each row sitting in a DataTable. 通常,我需要为位于DataTable中的每一行执行一次该工具。

I need to redirect the output of the command-line tool and display it in “my” app. 我需要重定向命令行工具的输出并将其显示在“我的”应用程序中。 I'm attempting to do so via a text box. 我试图通过文本框这样做。 I'm running into issues around updating the UI thread that I can't get straightened out on my own. 我正在遇到有关更新UI线程的问题,而我自己无法理解这些问题。

To execute my command-line tool, I've borrowed code from here: How to parse command line output from c#? 为了执行我的命令行工具,我从这里借用了代码: 如何解析c#的命令行输出?

Here is my equivalent: 这是我的等价物:

        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);

            }

        }

Referencing txtOuput.text when I instantiate my StringBuilder in cmd_DataReceived () neatly causes the app to hang: I'm guessing some sort of cross-thread issue. 当我在cmd_DataReceived()中实例化我的StringBuilder时,引用txtOuput.text整齐地导致应用程序挂起:我猜是某种跨线程问题。

If I remove the reference to txtOuput.text in StringBuilder and continue debugging, I get a cross-thread violation here: 如果我在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.

OK, not surprised. 好的,不要惊讶。 I assumed cmd_DataReceived is running on another thread since I'm hitting it as the result of doing stuff after a Process.Start() …and if I remove ALL references to txtOuput.Text in cmd_DataReceived() and simply dump the command-line text output to the console via Console.Write(), everything works fine. 我假设cmd_DataReceived正在另一个线程上运行,因为我在执行Process.Start()之后执行操作的结果...并且如果我在cmd_DataReceived()中删除对txtOuput.Text的所有引用并且只是转储命令行文本通过Console.Write()输出到控制台,一切正常。

So, next I'm going to try standard techniques for updating my TextBox on the UI thread using the information in http://msdn.microsoft.com/en-us/library/ms171728.aspx 所以,接下来我将尝试使用http://msdn.microsoft.com/en-us/library/ms171728.aspx中的信息在UI线程上更新我的TextBox的标准技术

I add a delegate and thread to my class: 我向我的班级添加了一个委托和线程:

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;

I add a procedure to update the text box: 我添加了一个更新文本框的过程:

 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;
        }
    }

I add another proc which calls the thread-safe one: 我添加另一个调用线程安全的proc:

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

    }

...and finally I call this mess within cmd_DataReceived like this: ...最后我把这个混乱称为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);


    }

}

…When I run this text, the textbox sits there dead as a doornail, not getting updated. ...当我运行这个文本时,文本框就像死角一样死了,没有得到更新。 My console window continues to update. 我的控制台窗口继续更新。 As you can see, I tried simplifying things a bit just by getting the textbox to display "foo" vs. the real output from the tool - but no joy. 正如你所看到的,我尝试通过让文本框显示“foo”与工具的实际输出来简化一些事情 - 但没有快乐。 My UI is dead. 我的UI已经死了。

So what gives? 什么给出了什么? Can't figure out what I'm doing wrong. 无法弄清楚我做错了什么。 I'm not at all married to displaying results in a textbox, btw - I just need to be able to see what's going in inside the application and I'd prefer not to pop up another window to do so. 我完全不喜欢在文本框中显示结果,顺便说一下 - 我只需要能够看到应用程序内部的内容,我不想弹出另一个窗口来执行此操作。

Many thanks. 非常感谢。

I think the issue is in this line: 我认为问题在于这一行:

cmdProcess.WaitForExit(); 

It's in the method ImportWorkbooks which is called from the Click event method of btnImport . 它位于从ImportWorkbooks的Click事件方法调用的btnImport方法中。 So your UI thread is blocked until the background process is completed. 因此,您的UI线程将被阻止,直到后台进程完成。

You're calling ImportWorkbooks from the UI thread. 您正在从UI线程调用ImportWorkbooks。 Then, in this method, you're calling "cmdProcess.WaitForExit()". 然后,在此方法中,您将调用“cmdProcess.WaitForExit()”。 So basically you're blocking the UI thread until the process has finished executing. 所以基本上你是阻止UI线程,直到进程完成执行。 Execute ImportWorkbooks from a thread and it should work, or remove the WaitForExit and use the process' "Exited" event instead. 从一个线程执行ImportWorkbooks它应该工作,或者删除WaitForExit并使用进程''Exited'事件。

one of the reasons you text box doesn't update is because you don't pass the string to your SetText method. 文本框不更新的原因之一是因为您没有将字符串传递给SetText方法。

You don't need to create a thread. 您不需要创建线程。 Your implementation will of SetText will handle passing the call to from the worker thread (where cmd_DataReceived is called) to the UI thread. 您的SetText实现将处理从工作线程(其中调用cmd_DataReceived)到UI线程的调用。

Here is what I suggest you do: 我建议你这样做:

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);


    }

}

Also, you are blocking the UI thread as @Fischermaen mentioned when you call WaitForExit, you don't need it. 此外,当您调用WaitForExit时,您正在阻止UI线程@Fischermaen,您不需要它。

I would also suggest that you run ImportWorkbooks on a worker thread like so: (if you do this you can leave the call to 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM