.NET BackGroundWorker - InvalidOperationException:跨线程操作无效

时间:2011-11-01 06:19:47

标签: c# winforms multithreading

我有一个用.NET Winforms编码的项目。 我需要实现数据挖掘操​​作,将文本打印到TextBox并更新进度。

我尝试使用BackgroundWorker,但是它会抛出一个InvalidOperationException(跨线程操作无效:从'创建的线程以外的线程访问的控件'xxxxx')

为了缩小问题的潜在原因,我开始了一个新项目,包括以下内容: 按钮 - 启动BackgroundWorker 标签 - 打印文本。 和ProgressBar。

然而,结果是一样的。我搜索了SOF,并被告知要使用代表,但我不熟悉它。

这是抛出错误的代码示例:

using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace TestProject
{
    public partial class Form1 : Form
    {
        private readonly BackgroundWorker _bw = new BackgroundWorker();

        public Form1()
        {
            InitializeComponent();
            _bw.DoWork += RosterWork;
            _bw.ProgressChanged += BwProgressChanged;
            _bw.RunWorkerCompleted += BwRunWorkerCompleted;
            _bw.WorkerReportsProgress = true;
            _bw.WorkerSupportsCancellation = false;
        }

        private void RosterWork(object sender, DoWorkEventArgs doWorkEventArgs)
        {
            for (int i = 0; i < 1000; i++)
            {
                label1.Text = i.ToString();
                _bw.ReportProgress(Convert.ToInt32((i * (100 / 1000))));
            }
        }

        private void BwProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            progressBar1.Show();
            _bw.RunWorkerAsync();
        }

        private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            progressBar1.Hide();
        }

    }
}

更新 我遵循Jon Skeet的回答,它确实适用于我的测试项目,但回到我的真实项目,

我的表格布局:

表格 -    的TabControl       - Tab1        -Tab1Panel        -TextBox1

到达此行时:

TextBox txtbox1 = new TextBox();
Tab1Panel.Controls.Add(txtbox1);

当我以编程方式将Textbox添加到Panel Control时,仍会出现错误。

最后,我替换为:

 if (Tab1Panel.InvokeRequired)
     Tab1Panel.Invoke((MethodInvoker)delegate { Tab1Panel.Controls.Add(txtbox1); });
 else
     Tab1Panel.Controls.Add(txtbox1);

一切都是有效的。如何确定控件是InvokeRequired,是否指定了控件?

4 个答案:

答案 0 :(得分:11)

这是问题所在:

label1.Text = i.ToString();

您正在尝试更改未在UI线程上运行的BackgroundWorker中的标签文本。 BackgroundWorker的重点是在那里进行所有非UI工作,使用ReportProgress定期“返回”UI线程并使用您正在进行的进度更新UI。

所以要么你需要更改label1.Text中的BwProgressChanged你需要使用Control.Invoke / {{ 1}}就像你从任何其他后台线程那样:

BeginInvoke

有关复制部分的更多信息,请参阅Eric Lippert的博客文章"Closing over the loop variable considered harmful"。在这种特殊情况下,这只是一个问题,因为我正在使用// Don't capture a loop variable in a lambda expression... int copy = i; Action updateLabel = () => label1.Text = copy.ToString(); label1.BeginInvoke(updateLabel); 。此可以更改为:

BeginInvoke

...但是现在后台工作人员总是会等待UI继续前进,而现实生活中通常并不是你想要的。我通常更喜欢Action updateLabel = () => label1.Text = i.ToString(); label1.Invoke(updateLabel); 而不是BeginInvoke

答案 1 :(得分:4)

使用此代码即可使用

 BeginInvoke((MethodInvoker)delegate
               {
                   TextBox1.Text += "your text here";

               });

答案 2 :(得分:0)

您正在后台工作线程中访问您的标签 - 已在GUI线程上创建。 不允许从创建控件之外的线程访问Windows控件;因此你被给了例外。

您不应该直接访问标签,而是应该从您的线程中引发一个事件,并确保在正确的线程上调用它 - 它会向UI发出信号,以便您可以更改标签的内容。 / p>

答案 3 :(得分:0)

您必须从创建控件的线程中执行控件的方法。要从其他线程更新它们,请使用Invoke。可以找到如何完成此操作的示例here。您还应该阅读MSDN上的Multithreading with Forms and Controls