C# – 使用非主线程更新GUI

我有一个有类的程序

  • GUI
  • 上传
  • 和两个类之间的缓冲区 – 即用于在两个类之间进行通信。

Upload类使用Process来运行命令行FTP应用程序。 我想返回FTP应用程序生成的输出,以显示在GUI的textbox
我尝试使用以下被截断的代码。

Upload Class(beginProcess()是一个用于启动Thread的方法(此处未显示)):

 public delegate void WputOutputHandler(object sender, DataReceivedEventArgs e); class Upload { private WputOutputHandler wputOutput; beginProcess() { Process pr = new Process(); pr.StartInfo.FileName = @"wput.exe"; pr.StartInfo.RedirectStandardOutput = true; pr.StartInfo.UseShellExecute = false; pr.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; pr.OutputDataReceived += new DataReceivedEventHandler(OnDataReceived); pr.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceived); pr.Start(); pr.BeginOutputReadLine(); pr.WaitForExit(); } public void OnDataReceived(object sender, DataReceivedEventArgs e) { if(wputOutput != null) { wputOutput(sender, e); } } public event WputOutputHandler WputOutput { add { wputOutput += value; } remove { wputOutput -= value; } } } 

缓冲类:

 public void EventSubscriber() { uploadSession.WputOutput += Main.writeToTextBoxEvent; } 

主类:

 public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e) { if(this.textBox1.InvokeRequired) { MethodInvoker what now? } else { textBox1.Text = e.Data; } } 

正如您所看到的,当它进入Main方法的writeToTextBoxEvent时,我已经没有想法了。 我不确定使用自定义事件进行UI更新是否是最好的方法。 如果有人能指出我正确的方向,我将非常感激。

这个怎么样:

 public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e) { if(this.textBox1.InvokeRequired) { // In .Net 2.0 this.textBox1.BeginInvoke(new MethodInvoker(() => writeToTextBoxEvent(sender, e))); // In .Net 3.5 (above is also possible, but looks nicer) this.textBox1.BeginInvoke(new Action(() => writeToTextBoxEvent(sender, e))); } else { textBox1.Text = e.Data; } } 

这种方法对Richard解决方案的优势在于您不需要两次编写执行代码(在BeginInvoke()并再次在else路径中)。

更新

如果您使用.Net 3.5,请将其作为扩展方法:

 public static class ControlExtensions { public static void SafeInvoke(this Control control, Action action) { if (control.InvokeRequired) { control.Invoke(action); } else { action(); } } public static void SafeBeginInvoke(this Control control, Action action) { if (control.InvokeRequired) { control.BeginInvoke(action); } else { action(); } } } 

并以这种方式使用它:

 public void writeToTextBoxEvent(object sender, System.Diagnostics.DataReceivedEventArgs e) { // Write it as a single line this.textBox1.SafeBeginInvoke(new Action(() => textBox1.Text = e.Data)); this.textBox1.SafeBeginInvoke(new Action(() => { //Write it with multiple lines textBox1.Text = e.Data; })); } 
 if(this.textBox1.InvokeRequired) { Action a = () => { textBox1.Text = e.Data; }; textBox1.Invoke(a); } 

即使用Invoke方法使用委托(可以是用于捕获局部变量的闭包)。 这将调度委托在文本框的线程上执行。 当委托完成执行时,将返回Invoke

当您不需要等待时( BeginInvoke ),还有一个异步版本。

编辑:忘记因为Invoke接受Delegate类型推断失败,所以使用本地。 注意,您当然可以在较早的时候创建委托,并在两个分支中使用它以避免重复的代码。

这是一种流行的扩展机制:

 public static void InvokeIfRequired(this System.Windows.Forms.Control c, Action action) { if (c.InvokeRequired) { c.Invoke((Action)(() => action())); } else { action(); } } 

用法:

 public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e) { this.textBox1.InvokeIfRequired(() => { textBox1.Text = e.Data; } } 

如果您需要响应式UI,则需要工作线程和主线程来更新UI。

我认为您需要一个工作线程来查找正在接收的数据,然后从您的工作线程使用委托将数据写入您的TextBox。

就像是 :

 public delegate void textBoxWriteDelegate(string msg); private void textBoxWrite(string sMess) { textBox.AppendText(sMess); } 

从你的工作线程:

 Invoke(new textBoxWriteDelegate(textBoxWrite), new object [] { "Launching ftp cmd line .... \n" }); 

对不起,这是净1.1;)有一个更好的方式来编写2.0及更高版本的代表…

我为此创建了一个小帮手:

 class LayoutsEngine { internal static void ThreadSafeDoer(Form Form, Delegate Whattodo) { if (!Form.IsDisposed) { if (Form.InvokeRequired) { Form.Invoke(Whattodo); } else { Whattodo.DynamicInvoke(); } } } } 

我正在使用它:

  LayoutsEngine.ThreadSafeDoer(this, new MethodInvoker(delegate() { txtWhatever.Text=whatever(); // and any other GUI meddling })); 

此外,如果有人有办法在这里减少MethodInvoker() ,请评论。