Thread and GUI update problem

J

Jason Jacob

To all,

I have a GUI program (use c#), and I have create a Thread for loading
some bulk data, I also arrange the GUI program like this:

1) load a form showing "Wait for loading..." etc
2) a Thread is then created to load the bulk data
3) after the thread has completed, close the "Wait for loading" form
4) show the main form for the GUI program

The problem is that if I show the "waiting" form, that form's GUI will
not work properly (no repaint event and hangs around, ie. a blank
window), the worse thing is that the Thread may not work properly too
(actually it will stop executing, waiting for locks, maybe ??)

But if I start the Thread without showing any forms; it works
perfectly ????!

What's wrong ?? (the same things happen under .Net Compact Framework)

[code snippet for the main GUI form]
public class Trial04_02 : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;
private Trial04_01 mFrmSplash;
private Thread mThr_Main;
private System.Windows.Forms.ListBox listBox1;
private CommonEngine02 mCEng;

public Trial04_02()
{
InitializeComponent();
init ();
}

private void InitializeComponent() {}

protected void init ()
{
// 1st show splash form
this.mFrmSplash = new Trial04_01 ();
//this.mFrmSplash.Show ();

// 2nd create a thread to load sth...
// CommonEngine02 is a class containing the
// data loading function
this.mCEng = new CommonEngine02 (); this.mThr_Main = new Thread
(new ThreadStart (this.mCEng.threadTask));
this.mThr_Main.Start ();
this.mThr_Main.Join ();
this.mFrmSplash.Close ();

// 3rd other setup(s)
// get back the loaded data
System.Collections.ArrayList oArr
= this.mCEng.getArr_Data ();
for (int i=0; i<oArr.Count; i++)
{
this.listBox1.Items.Add (oArr);
}
this.Show ();
}

static void Main ()
{
Application.Run (new Trial04_02 ());
}
}
}

[/code]


From Jason (Kusanagihk)
 
G

Guest

Hi Jason,

It looks like your form "mFrmSplash" (main thread) waits at
"this.mThr_Main.Join()" line, so it can not be "invalidated".

I can propose:
A) "mFrmSplash.Close() && this.Show()" within
thread "mThr_Main".
or
B) run third thread that will wait ("this.mThr_Main.Join()")
and then will close the splash form and show the main window.

Regads

Marcin
 
J

Jon Skeet [C# MVP]

Jason Jacob said:
I have a GUI program (use c#), and I have create a Thread for loading
some bulk data, I also arrange the GUI program like this:

1) load a form showing "Wait for loading..." etc
2) a Thread is then created to load the bulk data
3) after the thread has completed, close the "Wait for loading" form
4) show the main form for the GUI program

There's no point in creating a new thread if you're immediately going
to call Join on it. Instead, you need to run the UI thread as normal,
and have some kind of call when the load finishes to tell the loading
screen to close.

See http://www.pobox.com/~skeet/csharp/multithreading.html for more
information.
 
J

Jason Jacob

To Jon,

I've changed the code as you've said, actually there is no change in
the problem:

public Form1()
{
InitializeComponent();

oFrm2 = new Form2 ();
Thread t1 = new Thread
(new ThreadStart (oFrm2.threadTask));
t1.Start ();

loaderTask ();
oFrm2.mBool_Stop = true;
lock (this)
{
t1.Join ();
oFrm2.Close ();
}
}

The above code is the constructor code for the operation, threadTask
will change the Splash Form's Label's Text, but when got them
executed, the Splash Form's UI is not updating properly...

But then, I've tried to use oFrm2.ShowDialog () instead of oFrm2.Show
(), the result is that the Splash Form can be shown properly, but
actually it is not a Thread, since the form is waiting for you to
close it (Dialog), then it will continue the other tasks started
earlier...

From Jason (Kusanagihk)
 
J

Jon Skeet [C# MVP]

Jason Jacob said:
I've changed the code as you've said, actually there is no change in
the problem:

public Form1()
{
InitializeComponent();

oFrm2 = new Form2 ();
Thread t1 = new Thread
(new ThreadStart (oFrm2.threadTask));
t1.Start ();

loaderTask ();
oFrm2.mBool_Stop = true;
lock (this)
{
t1.Join ();
oFrm2.Close ();
}
}

The above code is the constructor code for the operation, threadTask
will change the Splash Form's Label's Text, but when got them
executed, the Splash Form's UI is not updating properly...

The constructor is not going to finish executing until the thread has
finished though, so you aren't starting a message pump.
But then, I've tried to use oFrm2.ShowDialog () instead of oFrm2.Show
(), the result is that the Splash Form can be shown properly, but
actually it is not a Thread, since the form is waiting for you to
close it (Dialog), then it will continue the other tasks started
earlier...

In the above, you aren't even showing oFrm2 at all. You should start
the thread and then have that tell the splash screen to close when it's
finished. Using Thread.Join is *not* a good idea when called from the
UI thread.
 
J

Jason Jacob

To Jon,

I've modified the code, and start the Thread outside the constructor.
Now it is works a bit "normal" to me. However the Splash Form still
can't change its GUI's properties (eg. I want to change a Label's text
showing the Time elapsed from loading etc..., it still won't update
for now)

And Join a UI Thread is not a *good* idea, what does that mean ??

the new code
Code:
namespace Splash_Trial.Trial02
{
public class Form1 : System.Windows.Forms.Form
{
..........

// Constructor
public Form1()
{
InitializeComponent();

this.Show ();
}

protected void loaderTask ()
{
// do some bulky things such as reading lots of data
...............
}

protected override void Dispose( bool disposing ){ //.... }

protected void threadTask ()
{
oFrm2 = new Form2 ();
Thread t1 = new Thread
(new ThreadStart (oFrm2.threadTask));
t1.Start ();

loaderTask ();  // the bulky work
oFrm2.mBool_Stop = true;
t1.Join ();
oFrm2.Close ();
}

[STAThread]
static void Main()
{
Form1 oFrm1 = new Form1 ();
oFrm1.threadTask ();
Application.Run(oFrm1);
}
}
}

From Jason (Kusanagihk)
 
J

Jon Skeet [C# MVP]

Jason Jacob said:
I've modified the code, and start the Thread outside the constructor.
Now it is works a bit "normal" to me. However the Splash Form still
can't change its GUI's properties (eg. I want to change a Label's text
showing the Time elapsed from loading etc..., it still won't update
for now)

You need to use Control.Invoke or Control.BeginInvoke to update the UI
from a non-UI thread. Did you read the article I pointed you to before?
It has an example of doing this kind of thing.
And Join a UI Thread is not a *good* idea, what does that mean ??

It means you shouldn't do it, because your app's UI won't be able to
process any events (like painting) until the other thread has finished.
 
J

Jason Jacob

To Jon

I've read the "Threading in Windows Form" from your link. I have
modified the code to use Control.Invoke, but still some strange
things. If I don't add a Refresh () code, the UI won't update; if I
try to click or do anything on the splash form, the UI will not
respond (a "not responding" appears on the form's title bar.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

using System.Threading;

namespace ThreadCookBook.Trial05
{
public class Form2 : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
private System.ComponentModel.Container components = null;
public delegate void invokeTask ();
public invokeTask mInvokeTask;
private bool mBool_Stop = false;

public Form2()
{
InitializeComponent();
this.mInvokeTask = new invokeTask
(this.realInvokeTask);
}

public void realInvokeTask ()
{
Random ran = new Random
((int) System.DateTime.Now.Ticks);
int iR = ran.Next (255);
int iG = ran.Next (255);
int iB = ran.Next (255);

this.label1.Text = System.DateTime.Now + "";
this.BackColor = Color.FromArgb (iR, iG, iB);
this.Refresh ();
}

protected override void Dispose( bool disposing ) { //... }
private void InitializeComponent() { //... }

public void threadTask ()
{
if (this.Visible == false)
{
this.Show ();
}
while (!this.mBool_Stop)
{
Invoke (this.mInvokeTask);
Thread.Sleep (2000);
}
}

public void setStop (bool flag)
{
this.mBool_Stop = flag;
}
}
}


From Jason (Kusanagihk)
 
J

Jon Skeet [C# MVP]

Jason Jacob said:
I've read the "Threading in Windows Form" from your link. I have
modified the code to use Control.Invoke, but still some strange
things. If I don't add a Refresh () code, the UI won't update; if I
try to click or do anything on the splash form, the UI will not
respond (a "not responding" appears on the form's title bar.

Well you're now not showing the code which creates the new thread.
Please post a *complete* program which demonstrates the problem, and
we'll be able to get somewhere.
 
J

Jason Jacob

To Jon

The Main Form's code

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;

namespace ThreadCookBook.Trial05
{
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.ListBox listBox1;
private System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;
private Thread mThr_ThreadTask;
// a class to load some bulky data
private CommonEngine02 mCEng02;
// the Splash Form
private Form2 mFrm02;

public Form1()
{
InitializeComponent();
init ();
}

protected override void Dispose( bool disposing ) { //... }
private void InitializeComponent() { //... }

protected void init ()
{
this.mCEng02 = new CommonEngine02 ();
}

protected void loaderTask ()
{
this.threadTask ();
// blocking, load the bulky data ...
this.mCEng02.threadTask ();
// try to stop the loop in the Splash Form
lock (this)
{
this.mFrm02.setStop (true);
this.mFrm02.Close ();
}
// get back the bulky data and add them to a listBox
ArrayList oArr = this.mCEng02.getArr_Data ();
for (int i=0; i<oArr.Count; i++)
{
this.listBox1.Items.Add (oArr);
}
}

public void threadTask ()
{
this.mFrm02 = new Form2 ();
this.mThr_ThreadTask = new Thread
(new ThreadStart (this.mFrm02.threadTask));
this.mThr_ThreadTask.Start ();
}

static void Main ()
{
// maybe here's where the problem is...
Form1 oFrm01 = new Form1 ();
oFrm01.loaderTask ();
Application.Run (oFrm01);
}
}
}

From Jason (Kusanagihk)
 
J

Jon Skeet [C# MVP]

Jason Jacob said:
The Main Form's code

That's *still* not a complete example.

However, the problem certainly does seem to be that you're doing all
the actual work before calling Application.Run, so you're not starting
the message pump on the thread until rather late.

Also, although you're *creating* an instance of Form2, you're never
showing it, as far as I can see.

By the way, I'd strongly recommend using MS's naming conventions:
http://tinyurl.com/2cun

You might also want to think about giving your classes more meaningful
names :)
 
J

Jason Jacob

To Jon

Um... I think I may need a simple guideline to implement Splash
Screen(s). Since my prior trials are on a wrong track, I think it is
difficult to code the right things out........

According to your suggestion, it seems that I may need another program
(maybe a console app) to start 2 threads (1 is for the main form --
loading bulky things etc, the other thread is for the Splash Screen),
then somehow the extra app will control when to kill the Splash Form
and show the main form out.
(Is that correct ???)

Full of question marks.......

From Jason (Kusanagihk)
 
J

Jon Skeet [C# MVP]

Jason Jacob said:
Um... I think I may need a simple guideline to implement Splash
Screen(s). Since my prior trials are on a wrong track, I think it is
difficult to code the right things out........

According to your suggestion, it seems that I may need another program
(maybe a console app) to start 2 threads (1 is for the main form --
loading bulky things etc, the other thread is for the Splash Screen),
then somehow the extra app will control when to kill the Splash Form
and show the main form out.
(Is that correct ???)

No, you don't need another program at all. You need to have one thread
which is just running the UI, via Application.Run. It shows the splash
screen, and starts another thread. When the other thread has finished
what it needs to do, it signals that to the UI thread, which puts up
the real UI instead.

One tricky thing here is that you may need to use the version of
Application.Run which *doesn't* take a Form, as otherwise when that
Form is closed, you'll end up losing the message pump.

Alternatively, you could call Application.Run on the splash screen, and
then Application.Run on the main UI.

Alternatively (again) you could create the main UI form and have it as
non-visible, create the splash screen as visible, and call
Application.Run on the main UI form.

Here's a version which uses the middle idea:

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;

using Timer = System.Windows.Forms.Timer;

delegate void LoadFinishedHandler();

class Test
{

static void Main()
{
Loader loader = new Loader();
SplashScreen splash = new SplashScreen(loader);
Application.Run (splash);
Application.Run (new MainForm());
}
}

class SplashScreen : Form
{
ProgressBar progress;
Timer timer;

public SplashScreen (Loader loader)
{
loader.LoadFinished += new LoadFinishedHandler(LoadFinished);
ControlBox = false;
Size = new Size(200, 80);
StartPosition = FormStartPosition.CenterScreen;

Label lbl = new Label();
lbl.Text = "Please wait";
lbl.Size = new Size (100, 20);
lbl.Location = new Point (10, 20);
Controls.Add(lbl);

progress = new ProgressBar();
progress.Minimum = 1;
progress.Maximum = 5;
progress.Step = 1;
progress.Value = 1;
progress.Size = new Size (160, 20);
progress.Location = new Point (10, 50);
Controls.Add(progress);

timer = new Timer();
timer.Interval = 500;
timer.Tick += new EventHandler (TimerTick);
timer.Enabled = true;

loader.Start();
}

void TimerTick(object sender, EventArgs e)
{
if (progress.Value == progress.Maximum)
{
progress.Value = progress.Minimum;
}
else
{
progress.PerformStep();
}
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
timer.Dispose();
}

void LoadFinished()
{
if (InvokeRequired)
{
Invoke(new LoadFinishedHandler(LoadFinished));
return;
}
Close();
}
}

class MainForm : Form
{
public MainForm()
{
Size = new Size(300, 300);
StartPosition = FormStartPosition.CenterScreen;

Label lbl = new Label();
lbl.Text = "Main UI goes here";
lbl.Size = new Size (100, 20);
lbl.Location = new Point (10, 20);
Controls.Add(lbl);
}
}

class Loader
{
object stateLock = new object();
LoadFinishedHandler loadFinishedDelegate;

public event LoadFinishedHandler LoadFinished
{
add
{
lock (stateLock)
{
loadFinishedDelegate += value;
}
}
remove
{
lock (stateLock)
{
loadFinishedDelegate -= value;
}
}
}

protected virtual void OnLoadFinished()
{
LoadFinishedHandler handler;
lock (stateLock)
{
handler = loadFinishedDelegate;
}
if (handler != null)
{
handler();
}
}

public void Start()
{
new Thread (new ThreadStart(Load)).Start();
}

void Load()
{
// Obviously you'd do real stuff here
Thread.Sleep(5000);
OnLoadFinished();
}
}
 
J

Jason Jacob

To Jon

Thanks for your advice, I am now your code example to implement the
splash screen and it work fine!

This problem really has taken me too much time.......

Thanks!

From Jason (Kusanagihk)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top