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

时间:2010-10-15 12:31:02

标签: c# multithreading user-interface

我有一个包含Classes的程序

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

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

5 个答案:

答案 0 :(得分:3)

这个怎么样:

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

答案 1 :(得分:2)

if(this.textBox1.InvokeRequired)
{
   Action a = () => { textBox1.Text = e.Data; };
  textBox1.Invoke(a);
}

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

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

如果您需要响应式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及更高版本的代表......

答案 4 :(得分: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(),请发表评论。