views:

378

answers:

2

Hi! I'm new working with background worker in C#. Here is a class, and under it, you will find the instansiation of it, and under there i will define my problem for you:

I have the class Drawing:

class Drawing
{
    BackgroundWorker bgWorker;
    ProgressBar progressBar;
    Panel panelHolder;

    public Drawing(ref ProgressBar pgbar, ref Panel panelBig)  // Progressbar and panelBig as reference
    {
        this.panelHolder = panelBig;
        this.progressBar = pgbar;
        bgWorker = new BackgroundWorker();
        bgWorker.WorkerReportsProgress = true;
        bgWorker.WorkerSupportsCancellation = true;

        bgWorker.DoWork += new OpenNETCF.ComponentModel.DoWorkEventHandler(this.bgWorker_DoWork);
        bgWorker.RunWorkerCompleted += new OpenNETCF.ComponentModel.RunWorkerCompletedEventHandler(this.bgWorker_RunWorkerCompleted);
        bgWorker.ProgressChanged += new OpenNETCF.ComponentModel.ProgressChangedEventHandler(this.bgWorker_ProgressChanged);
    }

    public void createDrawing()
    {
        bgWorker.RunWorkerAsync();
    }

    private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       Panel panelContainer = new Panel();

          // Adding panels to the panelContainer
          for(i=0; i<100; i++)
          {
            Panel panelSubpanel = new Panel();
            // Setting size, color, name etc....

             panelContainer.Controls.Add(panelSubpanel);  // Adding the subpanel to the panelContainer

             //Report the progress
             bgWorker.ReportProgress(0, i); // Reporting number of panels loaded
          }

          e.Result = panelContainer;   // Send the result(a panel with lots of subpanels) as an argument 
    }

    private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
          this.progressBar.Value = (int)e.UserState; 
          this.progressBar.Update();
    }

    private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            this.panelHolder          = (Panel)e.Result; 
        }
        else
        {
            MessageBox.Show("An error occured, please try again");
        }
    }

}

Instansiating an object of this class:

public partial class Draw: Form
{
  public Draw()
  {


      ProgressBar progressBarLoading = new ProgressBar();
      // Set lots of properties on progressBarLoading 

      Panel panelBigPanelContainer = new Panel();          

      Drawing drawer = new Drawing(ref progressBarLoading, ref panelBigPanelContainer);

      drawer.createDrawing(); // this makes the object start a new thread, loading all the panels into a panel container, while also sending the progress to this progressbar.
  }

}

Here is my problem: In the private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

i don't get the e.Result as it should be. When i debug and look at the e.Result, the panel's properties have this exception message:

'((System.Windows.Forms.Control)(e.Result)).ClientSize' threw an exception of type    'System.ObjectDisposedException'

So the object gets disposed, but "why" is my question, and how can i fix this?

I hope someone will answer me, this is making me crazy. Another question i have: Is it allowed to use "ref" with arguments? is it bad programming?

Thanks in advance.

I have also written how i understand the Background worker below here:


This is what i think is the "rules" for background workers:

bgWorker.RunWorkerAsync();   => starts a new thread.
bgWorker_DoWork cannot reach the main thread without delegates

-

private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
       // The work happens here, this is a thread that is not reachable by 
          the main thread

       e.Result  => This is an argument which can be reached by
                    bgWorker_RunWorkerCompleted()


       bgWorker.ReportProgress(progressVar);  => Reports the progress to the
                                                 bgWorker_ProgressChanged()           

}

-

    private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
           // I get the progress here, and can do stuff to the main thread from here 
              (e.g update a control)

              this.ProgressBar.Value = e.ProgressPercentage;
     }

-

    private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // This is where the thread is completed.
        // Here i can get e.Result from the bgWorker thread
        // From here i can reach controls in my main thread, and use e.Result in my main thread


        if (e.Error == null)
        {
            this.panelTileHolder  = (Panel)e.Result;

        }
        else
        {
            MessageBox.Show("There was an error");
        }
    }
A: 

You are creating UI controls (Panel) on a different thread and returning the container panel back to the main thread. UI controls have thread affinity. When the backgroundworker completes, the thread it uses gets released back to the thread pool and during that process, the UI controls associated with that thread are apparently disposed. When later you attempt to use a disposed panel object in RunWorkerCompleted event handler in your main thread, you get ObjectDisposedException.

You need to create those panels in your main thread where your UI is. You can create them in ProgressChanged event handler which runs in the main thread or you can call a different method that checks if InvokeRequired and if it does, then invokes the operation on the main thread by calling Invoke method. You can hide these panels until all of them are created and in RunWorkerCompleted event handler you can show them.

I suggest you take a look at the below blogpost.

WinForms UI Thread Invokes: An In-Depth Review of Invoke/BeginInvoke/InvokeRequred

Mehmet Aras
I was hoping to not get into invoking, but it looks like i have to do it that way....
+1  A: 

I can't follow your code, "imagePanel" seems to fall from the sky without any notion how it got created. What you are doing is however quite illegal, Windows requires the Parent of a control (set by your Controls.Add() call) to be a window that was created in the same thread as the child. .NET 2.0 normally checks for this and generates an IllegalOperationException when you violate that rule, hard to guess why they would leave that out of CF. If they in fact did.

ObjectDisposedException is common with BackgroundWorker when its RunWorkerCompleted or ProgressChanged event runs and the form was closed. You always have to make sure to cancel the BGW before you allow the form to disappear. That's kinda irrelevant here, you have to completely redesign this anyway.

Hans Passant
My mistake...."imagePanel" should be "panelContainer"
I'll look into invoking this anyway now... it's just that i read somewhere that using Background worker was doing the invoking for you.I was using the regular Thread before, but a friend of me told me that Background worker was so fantastic that i had to try it...
It stops being fantastic when you do a lot of invoking and actually run the majority of the code on the UI thread. Then you are just slowing down your code. A lot. Ask your friend about it.
Hans Passant
So you would recommend using regular threading? System.Thread
No, you can't create controls in any kind of thread. You can collect the data you want to display in a control, that's about it. What problem are you *really* trying to solve?
Hans Passant
Well... I want a user to be able to load lots of panels into one panel(the hard work), by pushing "create" button.And then the user should be able to load the panels into a Main panel which holds them. I want to thread this, so that the user can be able to cancel the "create" action. Cause if i do this in a main thread, the user have to wait for the "create" action to complete.