【问题标题】:C# - Updating GUI using non-main ThreadC# - 使用非主线程更新 GUI
【发布时间】:2011-04-25 22:15:50
【问题描述】:

我有一个有类的程序

  • 图形界面
  • 上传
  • 和两个类之间的缓冲区——即用于在两个类之间进行通信。

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

上传类(beginProcess() 是用于启动线程的方法(此处未显示)):

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 更新是否是最好的方法。如果有人能指出我正确的方向,我将不胜感激。

【问题讨论】:

标签: c# multithreading user-interface


【解决方案1】:

这个怎么样:

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

【讨论】:

  • 无法将 lambda 表达式转换为类型“System.Delegate”,因为它不是委托类型
  • @johnnyturbo3 查看我对答案的更新:在这种情况下类型推断失败,只需创建一个 Action 实例并传递它。
【解决方案2】:
if(this.textBox1.InvokeRequired)
{
   Action a = () => { textBox1.Text = e.Data; };
  textBox1.Invoke(a);
}

即通过Invoke 方法使用委托(可以是捕获局部变量的闭包)。这将分派要在文本框线程上执行的委托。 Invoke 将在委托完成执行后返回。

还有一个不需要等待的异步版本(BeginInvoke)。

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 啧啧啧,这里需要注明出处。 stackoverflow.com/questions/2367718/…
    • 谢谢汉斯!我了解,但我不确定该来源是否是原始来源。有数百个类似的例子。 :) 但是是的,答案是一样的。
    【解决方案4】:

    如果你想要一个响应式 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" });
    

    抱歉,它是 net 1.1 ;) 在 2.0 及更高版本中有更好的方法来编写委托...

    【讨论】:

      【解决方案5】:

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

      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(),请发表评论。

      【讨论】:

      • 不,不可能。您只能在 3.5 中用更好的 Action 替换它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-30
      • 1970-01-01
      • 2021-05-16
      • 2017-02-27
      相关资源
      最近更新 更多