Click here to Skip to main content
15,886,578 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more: , +
Hi all,
I create a form which have some function perform on the form_load, at the meantime I want to show a form(like "please wait").
I used Background Worker.
My code is :-
C#
private void QuestionForm_Load(object sender, EventArgs e)
        {
            WP = new WorkerProgress();//here WorkerProgress is a form to display the message("Please wait").
            backgroundWorker1.RunWorkerAsync();           
        }
 private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            WP.Show();
            for (int i = 1; i <= Noq; i++)
            {
                Button lbl = new Button();
                lbl.Text = i.ToString();
                lbl.AutoSize = true;
                lbl.AutoSizeMode = AutoSizeMode.GrowAndShrink;
                lbl.Font = new Font("Comic Sans MS", 9, FontStyle.Bold);
                lbl.BackColor = Color.AntiqueWhite;
                lbl.Click += new EventHandler(lbl_Click);
                tableLayoutPanel1.Controls.Add(lbl); //error is here "Cross-thread operation not valid: Control 'tableLayoutPanel1' accessed from a thread other than the thread it was created on."
            }
            generateRnd();//this is a function
            getQues();//this is a function
            SetFirstQues();//this is a function
            label2.Text = mm.ToString("00") + ":" + ss.ToString("00");
            setToolTip();//this is a function
        }
        

        private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            WP.Dispose();
        }


How to solve this Error ?

Thanks in advanced....

Regard
Jayanta...
Posted

Hello Mam,

When ever we start a form based application, the application starts on a single UI thread.
Now if i perform some operation using some of its controls. Suppose, on button click, perform some calculation. If the calculation is small its okay, but as the work to be done grows, then your form will turn white and render a not responding message. This is because the UI thread cant simultaneously render contents and perform calculation.

So we need to divert the load from the main thread, perform that operation on some other background thread and the result will be returned to the UI. Meanwhile you will also come to know the progress of your work status.

I will show you a small demo with the help of which may be you can simulate the scenario according to your demand.

FORM code:

C#
public partial class Form1 : Form
   {
       public Form1()
       {
           InitializeComponent();
       }

       private void button1_Click(object sender, EventArgs e) ////This button starts the background worker thread
       {
           Class1 c = new Class1(label1); ////Initialize the class and pass it the label so that you can display the progress in terms of %
           c.init_background_worker().RunWorkerAsync(); ///initialize the  background worker and start it.

       }

       private void button2_Click(object sender, EventArgs e)
       {
           MessageBox.Show("Hello");         //// this button is given so that you can press this button again and again while the background worker operates. You will notice there is a smooth operation and no non responsiveness
       }
   }



Now in a different class:

class Class1
   {
      static BackgroundWorker bw;
      Label L;
       public Class1(Label l)
       {
           L = l;

       }


       public BackgroundWorker init_background_worker()
       {

           bw = new BackgroundWorker();
           bw.DoWork += new DoWorkEventHandler(dowork);
           bw.ProgressChanged += new ProgressChangedEventHandler(changeinprogress);
           bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(workdone);
           bw.WorkerReportsProgress = true; ///this tells the background worker to report progress.
           return bw;
       }

       public void dowork(object sender, DoWorkEventArgs e)
       {
           for (int i = 0; i < 10000; i++)
           {
               Thread.Sleep(200);
               if (i == 999)
               {
                   bw.ReportProgress(100);


               }
                   bw.ReportProgress(i); ////the report progress method is passed the index i, so that as the loop iterates the value of i can be taken as a unit percentage

           }
      }

       public void changeinprogress(object sender,ProgressChangedEventArgs e)
       {
           L.Text = e.ProgressPercentage.ToString() + "%"; ////displayes the change in progress, this even is triggered when you call the ReportProgress() method.

       }

       public void workdone(object sender, RunWorkerCompletedEventArgs e)
       {
           L.Text = "Done"; //// when entire operation completes hello is displayed on the label.

       }


   }



}


Thanks,
- Rahul
 
Share this answer
 
Comments
JayantaChatterjee 7-Jan-14 8:54am    
okay.
if i create a thread class and access all the controls(UI) which are needed to manipulate then it will be complicated..
how I accessed tableLayoutPanel1 Controls from another thread??
Rahul VB 7-Jan-14 12:18pm    
Hello Mam,
Yes i have created a complicated code, sorry for that. I have this habit of doing it in most of my projects. See, sometimes you need to seperate out functionalities according to classes. Because, as your code grows it would be impossible to write down each method in one class. For example you will want to write various methods in the Form class, eventually you will get confused. Its not complicated at all:

Control var;
public class(Control variable)
{

var = variable;
}

public void method()
{

////do anything with the control variable which was referenced by var.
}


OR

public class Class1 : Form1
/// use the partial class technique to access the properties of the Form1. But take care of the instance or static properties.

OR

create a thread:

Say :
Thread t = new Thread(()=>dowork(tableLayoutPanel1));////pass your tableLayoutPanel1 to the thread as the function passed to the thread accepts a tableLayoutPanel. Now in a seperate class simply write down the methods you want to work with. And in the main class simply initialize the thread and pass the method which you want to start in the background to the thread.


///set the background property of the thread to true: t.IsBackground = true;
/// now start the thread, this behaves like a background thread.
public void dowork(tableLayoutPanel tbl)
{
////now use the tbl variable to do what ever you want to do with the tableLayoutPanel control

}

i hope i have not confused you.

Thanks,
- Rahul
JayantaChatterjee 10-Jan-14 9:27am    
I clearly understand what you said , thanks for the Help..

I tried Your Last Example :-
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(() => doWork(tableLayoutPanel1));
t.IsBackground = true;
t.Start();

}
public void doWork(TableLayoutPanel TLP)
{
for (int i = 0; i < 30; i++)
{
Button btn = new Button();
btn.Text = (i + 1).ToString();
btn.AutoSize = true;
TLP.Controls.Add(btn);//same error here
}
}

and I also tried to send tableLayoutPanel1 as an argument in backgroundWorker1.
like :- backgroundWorker1.RunWorkerAsync(tableLayoutPanel1);
the event is :-
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
TableLayoutPanel tlp = (TableLayoutPanel)e.Argument;
for (int i = 0; i < 30; i++)
{
Button btn = new Button();
btn.Text = (i + 1).ToString();
btn.AutoSize = true;
tlp.Controls.Add(btn);//Cross-thread operation not valid
}
}
same error here..
nothing improved .....
Hi Jayanta,
I was reading the discussion you were having with OriginalGriff and I wonder if the real problem is the poor performance of Microsoft's TableLayoutPanel. If you are having issues with redraw then enabling double buffering on the control will speed it up. I have found that on .NET version 2 the difference is really noticeable.

The DoubleBuffered property is protected and to set it to true you have to create a derived class and make the change in the constructor.

This is the code I have in my library of useful stuff.
C#
/// <summary>
/// DoubleBuffered = true improves redraw performance dramatically
/// </summary>
/// <remarks>As false gives such poor performance it is difficult
/// to understand why Microsoft made it the default.</remarks>
public class ImprovedTableLayoutPanel : System.Windows.Forms.TableLayoutPanel {
  public ImprovedTableLayoutPanel()
    : base() {
    DoubleBuffered = true;
  }
}


Double buffering, if you don't know, changes the way that the screen is updated. Instead of drawing directly to the video adapter, it will now be done in two steps. First the changes are written to an image in a memory buffer and then the image is copied to the adapter using a technique called bit blitting. As writing to main memory is fast, and video adapters are very good at bit blitting, the two step double buffered process is usually better then the direct way.

Let me know if this is useful, or if you need more information then just ask and I'll try to help.

Alan.
 
Share this answer
 
Comments
JayantaChatterjee 11-Jan-14 21:48pm    
My Vote of 5+..
Thank you Sir, its works fine...
Thanks again....
You cannot access controls exactly from the thread that they were created on: the UI thread.
You are trying to access them from your BackgroundWorker, which is a different thread entirely.
Either use the Invoke method to access them, or move the code into the UI thread via the BackgroundWorker.ReportProgress method.

"Sir, sorry for the missing info..
I tested this all methods loops with 30 (means Noq=30).
If I do it with 100 then it will be getting longer time(more then 4 min.) to execute ...
then the problem will arise .... :-(
How to solve this???"


A quick test gives me different results to you - but that is to be expected. I get around 300ms, increasing by 150ms odd each time I press the button and add another 30 buttons, so it's likely that your layout panel is setup different to mine.

However, have you considered what you are doing here? Each time you add a control, the layout panel has a whole pile of work to do...

So, try again, still with your Stopwatch code in there, and see what happens with a simple mod:
C#
Stopwatch s1 = new Stopwatch();
s1.Start();
tableLayoutPanel1.SuspendLayout();
for (int i = 1; i <= Noq; i++)
    {
    Button lbl = new Button();
    lbl.Text = i.ToString();
    lbl.AutoSize = true;
    lbl.AutoSizeMode = AutoSizeMode.GrowAndShrink;
    lbl.Font = new Font("Comic Sans MS", 9, FontStyle.Bold);
    lbl.BackColor = Color.AntiqueWhite;
    lbl.Click += new EventHandler(lbl_Click);
    tableLayoutPanel1.Controls.Add(lbl); //error is here "Cross-thread operation not valid: Control 'tableLayoutPanel1' accessed from a thread other than the thread it was created on."
    }
tableLayoutPanel1.ResumeLayout();
s1.Stop();

You should find your numbers drop immediately to a consistent, smaller, value because you are only causing the layout panel to recalculate the once.
 
Share this answer
 
v2
Comments
JayantaChatterjee 7-Jan-14 3:15am    
Sir, create a method for that code, and called that method from BackgroundWorker DoWork event, but still its gives same error... :-(

can you please give me example of
how to move the code into the UI thread via the BackgroundWorker.ReportProgress method???
OriginalGriff 7-Jan-14 4:46am    
Creating a method does not move the code between threads - that's why the Thread and BackgroundWorker class exist: to move code to a different thread.

The problem is that the code you are running in the BackgroundWorker (BW for short) is *all* UI thread related - control construction and attachment to the display. So invoking it (or otherwise moving it to the UI thread is liable only to slow things down as you have all the complexity moving back to the UI thread while the BW thread will be doing very, very little except invoke the UI thread!

Why are you trying to do this in a separate thread? There doesn't seem a lot of point if all you are going to do is UI thread code anyway! Are you having a problem with speed on your main thread?
Rahul VB 7-Jan-14 6:52am    
Sir,
Absolutely, 5+ and a high five from my side.I have posted a solution please look into it.
Thanks,
- Rahul
Rahul VB 7-Jan-14 6:55am    
Sir,
One more thing, if i remove Thread.Sleep() then the form hangs. Why so?
Thanks,
- Rahul
OriginalGriff 7-Jan-14 7:36am    
Well...what else is it doing?
Think about it: if you remove the Thread.Sleep, then the background worker is just pumping "Progress reports" at the UI thread - 10,000 of them in very quick succession - so the UI is not absolutely tied up processing those. And since the UI thread code involves two memory allocations and a control update (which causes it's own Paint event which also has to be executed on the UI thread, but is low priority) it spends it's entire time updating the label, and getting ready to paint it, but never gets round to it. Since you probably have multiple cores, the BackgroundWorker is almost certainly running on a separate core and it's lower priority doesn't matter - it runs at full speed because the core is there to run it. And it gives the UI thread so much work to do it can't keep up!
Hello Mam,

look at the code below:

class Class1
{
static TableLayoutPanel tlp;
public Class1(TableLayoutPanel TLP)
{
tlp = TLP;

}


public void doWork(object sender,DoWorkEventArgs e)
{
////perform buisness logic or what ever.
}


public void work_done(object sender, RunWorkerCompletedEventArgs e)
{
for (int i = 0; i < 30; i++)
{
Button btn = new Button();
btn.Text = (i + 1).ToString();
btn.AutoSize = true;
tlp.Controls.Add(btn);//here the background thread returns to UI and render what you want
}


}



}


The form code:


C#
public partial class Form1 : Form
   {
       public Form1()
       {
           InitializeComponent();
       }
       BackgroundWorker bw = new BackgroundWorker();


       private void button1_Click(object sender, EventArgs e)
       {
           bw.RunWorkerAsync();<
       }

       public void fun()
       {
           Class1 c = new Class1(tableLayoutPanel1);
            bw.DoWork +=new DoWorkEventHandler(c.doWork);
           bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(c.work_done);


       }

       private void Form1_Load(object sender, EventArgs e)
       {
           fun();
       }

   }
 
Share this answer
 
v2
Comments
JayantaChatterjee 11-Jan-14 21:50pm    
this code gives same error in "Class1" methods..
Please run your code before post..
OriginalGriff 12-Jan-14 4:19am    
Sorry, Rahul, but you can't do that: you have to check your code works before you post it!
If you read up on threading (and that is what a BackgroundWorker uses) you can't safely do anything with UI controls except from the UI thread.

P.S. Use code blocks on all your code fragments, eh? :laugh:
Rahul VB 15-Jan-14 5:27am    
I apologize in advance to both of you. Hey OG, i again tried the code, in fact i copied and pasted the code in a fresh application, it works, on button click it renders 4 buttons and it doesnt give any error. I can write it in my blood that it works on my pc. I am sorry if i sound rude. I am just surprised. Hey OG please tell me whats wrong in this code?and sorry for late reply, i was just busy with work.

- Rahul
OriginalGriff 15-Jan-14 7:15am    
Not at the moment, because it's pretty complicated: a normal thread (i.e. not the UI thread) doesn't have a message loop and you will get some really nasty problems later if you create UI elements on a thread that doesn't have one.
For the moment, just take it as accepted wisdom: anything to do with controls (including creating them) needs to be on the UI thread.
Rahul VB 15-Jan-14 8:47am    
aye aye captain OG, as you say, i will dwell deeper into this.

-Rahul

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900