简体   繁体   English

C# BackgroundWorker 中的跨线程操作无效

[英]C# Cross-thread operation not valid in BackgroundWorker

I'm adding items to a listbox when the main form is loaded:加载主窗体时,我将项目添加到列表框:

private void MainForm_Load(object sender, EventArgs e)
{
    Dictionary<string, string> item = new Dictionary<string, string>();
    item.Add("Test 1", "test1");
    item.Add("Test 2", "test 2");

    cmbTest.DataSource = new BindingSource(item, null);
    cmbTest.DisplayMember = "Key";
    cmbTest.ValueMember = "Value";
}

Then I trying to get the selected item value in a BackgroundWorker but it's fails.然后我试图在 BackgroundWorker 中获取选定的项目值,但它失败了。

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
    MessageBox.Show(test);
}

The backgroundworker should not try to do anything with the UI.后台工作者不应尝试对 UI 做任何事情。 The only thing that the thread can do is notify those who are interested that the background worker calculated something noteworthy.线程唯一可以做的就是通知那些感兴趣的人后台工作人员计算了一些值得注意的事情。

This notification is done using the Event ProgressChanged.此通知是使用 Event ProgressChanged 完成的。

  • Use Visual Studio Designer to create a BackGroundWorker.使用 Visual Studio 设计器创建一个 BackGroundWorker。
  • Let designer add event handlers for DoWork, ProgressChanged, and if needed RunWorkerCompleted让设计师为 DoWork、ProgressChanged 和 RunWorkerCompleted 添加事件处理程序(如果需要)
  • If the BackGroundWorker wants to notify the form that something should be displayed, use ProgressChanged如果 BackGroundWorker 想要通知表单应该显示某些内容,请使用 ProgressChanged

You probably simplified your problem, but the backgroundworker should not read the selected value of the combo box.您可能简化了您的问题,但后台工作人员不应读取组合框的选定值。 If the combobox changes, the Form should start the backgroundworker while passing the value of the selected combobox item.如果 combobox 发生变化,表单应在传递所选 combobox 项的值时启动后台工作程序。

So let's make the problem a bit more interesting: if the user selects an item in comboBox1, the backgroundworker is ordered to calculate something with the selected combobox value.因此,让我们把问题变得更有趣一点:如果用户在 comboBox1 中选择了一个项目,后台工作人员将被命令使用所选的 combobox 值计算一些东西。

During the calculation the BackGroundWorker regularly notifies the form about progress and intermediate calculated values.在计算期间,BackGroundWorker 会定期通知表单有关进度和中间计算值的信息。 When it finishes, the end result is returned.完成后,返回最终结果。

The code will be like this:代码将是这样的:

private void InitializeComponent()
{
    this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
    this.SuspendLayout();
    this.backgroundWorker1.DoWork += new DoWorkEventHandler(this.DoBackgroundWork);
    this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(this.NotifyProgress);
    this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
                                                 this.OnBackgounrWorkCompleted);
    ...
}

When an item is selected comboBox1 the backgroundworker is started using the selected value. When an item is selected comboBox1 the backgroundworker is started using the selected value. While the backgroundworker is started, users can't change combobox1 again, because we can't start the same backgroundworker while it is still busy.当 backgroundworker 启动时,用户无法再次更改 combobox1,因为我们无法在它仍然忙碌时启动同一个 backgroundworker。

Therefore the combobox is disabled, and a progressbar is shown.因此 combobox 被禁用,并显示一个进度条。 During the calculations the progressbar is updated and intermediate results are shown in Label1.在计算过程中,进度条会更新,中间结果显示在 Label1 中。 When the backgroundworker finishes, the progressbar is removed, the final result is displayed in Lable1 and the combobox is enabled again.当 backgroundworker 完成后,进度条被移除,最终结果显示在 Lable1 中并再次启用 combobox。

Note that the rest of the form is still working while the backgroundworker is calculating.请注意,后台工作人员正在计算时,表单的 rest 仍在工作。

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    ComboBox comboBox = (ComboBox)sender;

    // disable ComboBox, show ProgressBar:
    comboBox.Enabled = false;
    this.progressBar1.Minimum = 0;
    this.progressBar1.Maximum = 100;
    this.progressBar1.Value = 0;
    this.progressBar1.Visible = true;

    // start the backgroundworker using the selected value:
    this.backgroundWorker1.RunWorkerAsync(comboBox.SelectedValue);
}

The Background work:背景工作:

private void DoBackgroundWork(object sender, DoWorkEventArgs e)
{
    // e.Argument contains the selected value of the combobox
    string test = ((KeyValuePair<string, string>)e.Argument;

    // let's do some lengthy processing:
    for (int i=0; i<10; ++i)
    {
        string intermediateText = Calculate(test, i);

        // notify about progress: use a percentage and intermediateText
        this.backgroundWorker1.ReportProgress(10*i, intermediateText);
    }

    string finalText = Calculate(test, 10);

    // the result of the background work is finalText
    e.Result = finalText;
}

Regularly your Form gets notified about the progress: let it update the ProgressBar and show the intermediate text in Label1您的表单会定期收到有关进度的通知:让它更新 ProgressBar 并在 Label1 中显示中间文本

private void NotifyProgress(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
    this.label1.Text = e.UserState.ToString();
}

When the BackgroundWorker completes, the final text is displayed in label1, the progressbar disappears and the Combobox is enabled again:当 BackgroundWorker 完成时,最终文本显示在 label1 中,进度条消失,Combobox 再次启用:

private void OnBackgoundWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.label1.Text = e.Result.ToString();
    this.progressBar1.Visible = false;
    this.comboBox1.Enabled = true;
}

The reason is the background worker runs on separate threads.原因是后台工作程序在单独的线程上运行。 You have to use UI thread to read values from UI thread.in this case, cmbTest is on the UI您必须使用 UI 线程从 UI 线程读取值。在这种情况下,cmbTest 在 UI 上

 this.Invoke(new Action(() =>
 {
   string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
 }));

If you need values to do async proccess如果您需要值来执行异步处理

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
 string test;
 this.Invoke(new Action(() =>
 {
     //Any other things you need from UI thread
     test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
 }));
 //Here you have access to UI thread values
}

The UI elements can only be accessed by the UI thread. UI 元素只能由 UI 线程访问。

If you are working with WinForms so you can use Control.InvokeRequired flag and Control.Invoke metod:如果您使用的是 WinForms,那么您可以使用Control.InvokeRequired标志和Control.Invoke方法:

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    if(!cmbTest.InvokeRequired)
    {
        string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
        MessageBox.Show(test);
    }
    else 
    {
        string test;
        Invoke(() => test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value);
        Invoke(() => MessageBox.Show(test));
    }
}

Control.InvokeRequired gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread than the one the control was created on. Control.InvokeRequired获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 invoke 方法,因为调用方所在的线程与创建控件的线程不同。

Control.Invoke executes the specified delegate on the thread that owns the control's underlying window handle. Control.Invoke在拥有控件的基础 window 句柄的线程上执行指定的委托。

If you are working with WPF than there is a solution for it too.如果您使用的是 WPF,那么也有解决方案。 Dispatcher.Invoke executes the specified delegate synchronously on the thread the Dispatcher is associated with: Dispatcher.Invoke在 Dispatcher 关联的线程上同步执行指定的委托:

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    this.Dispatcher.Invoke(() => {
        string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
        MessageBox.Show(test);
    });
}

You can read more about thread-safe calls to windows forms controls of about WPF Threading Model on Microsoft docs.您可以在 Microsoft 文档上阅读更多关于windows forms 控件的线程安全调用WPF Threading Model的信息。

Other solution.其他解决方案。

Declare form field:声明表单域:

private string _value;

Enter the value in this field from the UI control in the place where you run BackgroundWorker从运行BackgroundWorker的地方的 UI 控件中在此字段中输入值

private void RunWorkButton_Click(object sender, EventArgs e)
{
    _value = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
    testWorker.RunWorkerAsync();
}

Next, use this field in the DoWork method:接下来,在DoWork方法中使用这个字段:

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // use _value somehow
    string test = _value;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM