【问题标题】:Cross-thread operation occurs when Console output is being redirected to textbox [duplicate]将控制台输出重定向到文本框时发生跨线程操作[重复]
【发布时间】:2013-03-01 14:14:27
【问题描述】:

总的来说,我是 C# 和 GUI 开发的新手。如果这是一个老问题,请直接引导我到那个来源,我会记下这个问题。

我的情况:所以我正在编写一个 GUI,它将一个函数的控制台输出重定向到我的 Windows 应用程序表单中的文本框。控制台输出只是向用户显示设备序列号、当前软件版本等信息。

我的问题:这个表格效果很好,只是更新软件的过程需要几分钟,这会冻结我的表格,直到它完成。现在,我知道实施后台工作人员将缓解这个问题。但是,当我实现后台工作人员时,我收到以下错误。

跨线程操作无效:控件“TextBox”从创建它的线程以外的线程访问。

我的代码总结如下:

public class Form1 : Form
{
    this.backgroundWorker1.DoWork += new
    System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
         BackgroundWorker bw = sender as BackgroundWorker;     
         BigProcess(bw);
    }

    private void BigProcess(BackgroundWorker bw)
    {
        // lengthy operation that includes lots of
        Console.WriteLine("feedback stuff for the user");
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.backgroundWorker1.RunWorkerAsync();
    }
}

在这个项目中,我还有一个派生自 StringWriter 的类 TextBoxStreamWriter,它为我处理控制台输出的重定向。在TextBoxStreamWriter 中,我将覆盖WriteLine 方法和Write 方法。

这是我正在做的一个例子:

public override void WriteLine(string value)
{
      base.WriteLine(DateTime.Now.ToString(value));
      textBoxOutput.AppendText(value.ToString() + Environment.NewLine);
      writer.Write(value);
}

调用此方法时会抛出 InvalidOperationException。

我怎样才能使这个线程安全?任何帮助,将不胜感激。

【问题讨论】:

    标签: c# multithreading winforms


    【解决方案1】:

    您只需要将文本框的更新返回到 UI 线程 - 例如:

    if (textBoxOutput.InvokeRequired)
    {
      Invoke((MethodInvoker)(() => textBoxOutput.AppendText(value.ToString() + Environment.NewLine);
    }
    else
    {
       textBoxOutput.AppendText(value.ToString() + Environment.NewLine)
    }
    

    【讨论】:

    • 谢谢!效果非常好!
    • 很高兴为您提供帮助。 (顺便说一句,很好的详细问题,值得许多赞成。)
    • 您不应该从 BackgroundWorker 内部手动封送至 UI 线程。使用 BGW 的整个 要点 是它代表您处理 UI 编组,因此您不必这样做。如果您不打算利用任何该功能,那么您不妨使用Thread
    【解决方案2】:

    BackgroundWorker 公开了一个事件:RunWorkerCompleted,用于使用长时间运行操作的结果更新 UI。该事件将在 UI 线程中触发。

    所以您需要做的就是处理该事件,而不是手动编组到 UI 线程。

    backgroundWorker1.RunWorkerCompleted += (s, args)=> 
        textBoxOutput.AppendText(args.Result.ToString() + Environment.NewLine);
    

    然后您可以将DoWork 中的结果设置为您想要打印的任何内容。

    如果您想在整个后台处理过程中更新 UI,那么您实际上拥有“进度更新”。 BGW 内置了对在整个后台工作过程中更新 UI 的支持。有关示例,请参阅 BGW 的 MSDN 页面。

    【讨论】:

    • 这个答案确实帮助我进一步了解了如何有效利用后台工作人员。我在程序的现有命令行界面之上运行这个 GUI。该项目的目标是将代码量保持在最低限度,并且不改变现有结构。我觉得在这种情况下手动编组到 UI 线程是不可避免的。想要完成的只是从 CLI 获取进度更新并将它们映射到文本框(因此是 TextboxStreamWriter 类),它只需要在 BGW 完成后推送到 UI 线程。
    【解决方案3】:

    试试这个:

    public override void WriteLine(string value)
    {
        if (InvokeRequired)
        {
            Invoke(() => WriteLine(value));
            return;
        }
    
        base.WriteLine(DateTime.Now.ToString(value));
        textBoxOutput.AppendText(value.ToString() + Environment.NewLine);
        writer.Write(value);
    }
    

    这将检查是否从拥有表单的线程之外的线程调用 WriteLine。如果是,它将要求拥有表单的线程再次调用该方法,仅在正确的线程上。

    【讨论】:

    • 您不应该从 BackgroundWorker 内部手动封送至 UI 线程。使用 BGW 的整个 要点 是它代表您处理 UI 编组,因此您不必这样做。如果您不打算利用任何该功能,那么您不妨使用Thread
    猜你喜欢
    • 2011-02-22
    • 1970-01-01
    • 2010-09-29
    • 2013-11-14
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    • 2019-02-13
    相关资源
    最近更新 更多