thread hangs .. why ? + example project

  • Thread starter Thread starter Serge
  • Start date Start date
S

Serge

Hi,

I am having a thread hang problem in my c# code.

The example on the website: http://csharp.web1000.com/ is a
simplified version of my problem.

You will see in the form that a method TestThread increments a number in the
textbox on the form.
TestThread is called from a worker thread (2nd thread) using a TimerThread.
Every 2 seconds it will increment the number in te text box.

First start the project from the form (form1 class is the startup object).
The Form is displayed. Now push the button, this will create the
TimerThread.
You now see nicely that the number increments every 2 seconds.

Now start the application from the class (class1 is the startup object).
Now the TimerThread will be created in the Class1 object.
You will see that the number is never incremented.

When stepping through the code you will see that the TestThread method is
called.
The TestThread code in the form properly detects that this is run from a
second thread (line InvokeRequired) and continues to the line where Invoke
is called. I use Invole so that the increment function would run on the
forms thread. It is here where the application seems to hang. The number in
the textbox is never incremented.

Can anyone explain me why this is happening or how to solve this ?
i was wondering, might this have something to do with Apparementthreading ?
(this is used when you start the application from the form (=startup
object)).
Maybe when you start it from the class this is no longer the case ?
Maybe it's something completely different.
Thanks for shedding some light in the darkness.

Regards, Serge



http://csharp.web1000.com/
 
Hi Serge,

Application will hang for sure. When you use the Main method in Class1 as a
start point. There is no code starting the message loop (Application.Run).
At least Control.Invoke uses messages to switch the execution over the UI
thread. Further more if there is no message loop you cannot move, resize,
close or do anything with the form. Form is freezed on the screen. It won't
even repaint itself.

What you might want to do is.
1. Remove
_frmStatus.Show();
_frmStatus.Refresh();
from the code. You don't need them.
2. Insted of
while(true)
{
Thread.Sleep(1000);
}

put the following:
System.Windows.Forms.Application.Run(_frmStatus);

That makes the code almost like the one you have in the form's class.

HTH
B\rgds
100
 
Thanks 100
That helps me a lot. I already read about this message loop a couple of time
but now it's slowly all coming together.

Now just for the test I followed your suggestion and added the
Application.Run to my code so that the message loop would be started.
Specifically i added in my static Main method (from the class that is
defined as start object) the Application.Run line.
The problem I have here is that the code from Main method after the
Application.Run line is only executed when the form is closed again. And
that is not what I want.

I can not simply add this code in the form.
The reason for this is that this form is a simple status window.
It is shared by all my applications and contains no intelligent code. It
just displays the progress of the application.
The real work is done in the classes that are set as the start-up object.

So now I need a way to figure out how to start my application from my class
and still start this form's message loop using the application.run line.
Do you know a way ?
I can think of some solution using delegates but don't know if this is the
best practise.

Thanks in advance for your suggestions,

Regards,

Serge
 
forgot to explain in more detailed my reaction to your possible solution:

you said that it's better to have application.run instead of the

while(true)
{
Thread.Sleep(1000);
}

This is possible but in that case i create the status form after I start the
worker thread.
thereby I get into a problem becuase my worker thread might raise a status
event to be displayed on the status form before that the status form is
actually created.

i would prefer to create the status form first and then launch my worker
thread.

Thanks in advance for your suggestions,

Regards,
Serge
 
Hi Serge,
Yes, you are right. The code after Application Run will be executed only
after the main window (passed to Run method) as a parameter is closed. In
this case when the application is closed. You cannot have alive windwos in
Windwos without messeage loop to dispatch messages to it. What I want to say
is all GUI applications has to have at least one UI thread (for windows
forms this is thread that eventually calls Application.Run) All UI
commponents has to be created by an UI thread.
What you might want to do in your case is to have 2 threads: one worker
thread and one UI thread to dispalay the status. See my code below.
The changes are:
1. I removed the Main method from Form1 class. We don't need it anymore.
2. I Added two new methods in Form1 class: Run and CloseForm(It could be
just Close, but I had to hide the base one).
3. Modified Class1's Main method.
4. I use the old timer to update the status form, but you can use whatever
fits better with your needs. The only things is that the changes you make to
any UI components has to be made by the UI thread owning the component.
Which means that all threads but the UI thread itself has to use
Control.Invoke method.
//-------------------- Form1.cs -------------------------------//
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;

namespace _ThreadTest
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
public System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox txtForm;
private System.Windows.Forms.Label label1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.txtForm = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// txtForm
//
this.txtForm.Enabled = false;
this.txtForm.Location = new System.Drawing.Point(8, 8);
this.txtForm.Name = "txtForm";
this.txtForm.Size = new System.Drawing.Size(48, 20);
this.txtForm.TabIndex = 0;
this.txtForm.Text = "0";
this.txtForm.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// button1
//
this.button1.Location = new System.Drawing.Point(128, 8);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(192, 24);
this.button1.TabIndex = 1;
this.button1.Text = "Start Thread In Form Code - OK";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif",
7.764706F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
((System.Byte)(0)));
this.label1.Location = new System.Drawing.Point(128, 8);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(184, 24);
this.label1.TabIndex = 2;
this.label1.Text = "Thread started from code = BAD";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(336, 36);
this.Controls.Add(this.button1);
this.Controls.Add(this.txtForm);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Increment every 2 ses - thread test";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
public void Run()
{
Thread uiThread = new Thread(new ThreadStart(StatusThreadProc));
uiThread.ApartmentState = ApartmentState.STA;
uiThread.Start();
}


public void CloseForm()
{
if(InvokeRequired)
{
this.Invoke(new MethodInvoker(CloseForm));
}
this.Close();
}
/// <summary>Starts message loop (this thread is UI thread.)</summary>
public void StatusThreadProc()
{
Application.Run(this);
}

private void button1_Click(object sender, System.EventArgs e)
{
// Create the delegate that invokes methods for the timer.
TimerCallback timerDelegate = new TimerCallback(TestThread);

// Create a timer that waits one second, then invokes every second.
System.Threading.Timer timer = new System.Threading.Timer( timerDelegate,
"nothing" ,1000, 2000);

}

public void TestThread(object state)
{
if (! this.InvokeRequired )
{
base.Refresh();
txtForm.Text = (int.Parse(txtForm.Text) + 1).ToString();
}
else
{
Invoke(new WaitCallback(TestThread), new object[] {state});
}
}
}
}


//-------------------------------
Class1.cs ----------------------------------------
using System;
using System.Threading;


namespace _ThreadTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
public class Class1
{
private static Form1 _frmStatus;
private static TimerCallback timerDelegate;
private static System.Threading.Timer timer;


public Class1()
{
}

[STAThread]
static void Main(string[] args)
{

_frmStatus = new Form1();
_frmStatus.button1.Visible = false;
_frmStatus.Run();

// Create the delegate that invokes methods for the timer.
timerDelegate = new TimerCallback(_frmStatus.TestThread);

// Create a timer that waits one second, then invokes every second.
timer = new System.Threading.Timer( timerDelegate, "nothing" ,1000,
10000);


for(int i = 0; i < 20; i++)
{
//Do some job
Thread.Sleep(1000);
}
//Stop the close the status form. (This will exit the UI thread).
_frmStatus.CloseForm();

//You can do more work here

}
}
}


HTH
B\rgds
100
 
Possible the simplest way would be to create another form - MainForm -
which you will start with Application.Run
Form could be invisible - just hide it on load for example. Show your status
window from MainForm.Load.
Move your code (which is after Application.Run) into MainForm.Load.

Another way is to use threads as was suggested by other posters.

There are more advanced techniques of course, like creating message-only
window, which involves Win32 API (check HWND_MESSAGE - SetParent or
CreateWindowEx). Btw, this one could be used to transform your MainForm into
message-only window. It has some limitations, but could work for you too.

HTH
Alex
 
ok, got it

I now have my application working with the message pump running (using the
Application.Run) and my worker thread that I am able to configure
differently for each application (but re-using the status window)

Thanks VERY much !!!

100 said:
Hi Serge,
Yes, you are right. The code after Application Run will be executed only
after the main window (passed to Run method) as a parameter is closed. In
this case when the application is closed. You cannot have alive windwos in
Windwos without messeage loop to dispatch messages to it. What I want to say
is all GUI applications has to have at least one UI thread (for windows
forms this is thread that eventually calls Application.Run) All UI
commponents has to be created by an UI thread.
What you might want to do in your case is to have 2 threads: one worker
thread and one UI thread to dispalay the status. See my code below.
The changes are:
1. I removed the Main method from Form1 class. We don't need it anymore.
2. I Added two new methods in Form1 class: Run and CloseForm(It could be
just Close, but I had to hide the base one).
3. Modified Class1's Main method.
4. I use the old timer to update the status form, but you can use whatever
fits better with your needs. The only things is that the changes you make to
any UI components has to be made by the UI thread owning the component.
Which means that all threads but the UI thread itself has to use
Control.Invoke method.
//-------------------- Form1.cs -------------------------------//
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;

namespace _ThreadTest
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
public System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox txtForm;
private System.Windows.Forms.Label label1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.txtForm = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// txtForm
//
this.txtForm.Enabled = false;
this.txtForm.Location = new System.Drawing.Point(8, 8);
this.txtForm.Name = "txtForm";
this.txtForm.Size = new System.Drawing.Size(48, 20);
this.txtForm.TabIndex = 0;
this.txtForm.Text = "0";
this.txtForm.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// button1
//
this.button1.Location = new System.Drawing.Point(128, 8);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(192, 24);
this.button1.TabIndex = 1;
this.button1.Text = "Start Thread In Form Code - OK";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif",
7.764706F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
((System.Byte)(0)));
this.label1.Location = new System.Drawing.Point(128, 8);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(184, 24);
this.label1.TabIndex = 2;
this.label1.Text = "Thread started from code = BAD";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(336, 36);
this.Controls.Add(this.button1);
this.Controls.Add(this.txtForm);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Increment every 2 ses - thread test";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
public void Run()
{
Thread uiThread = new Thread(new ThreadStart(StatusThreadProc));
uiThread.ApartmentState = ApartmentState.STA;
uiThread.Start();
}


public void CloseForm()
{
if(InvokeRequired)
{
this.Invoke(new MethodInvoker(CloseForm));
}
this.Close();
}
/// <summary>Starts message loop (this thread is UI thread.)</summary>
public void StatusThreadProc()
{
Application.Run(this);
}

private void button1_Click(object sender, System.EventArgs e)
{
// Create the delegate that invokes methods for the timer.
TimerCallback timerDelegate = new TimerCallback(TestThread);

// Create a timer that waits one second, then invokes every second.
System.Threading.Timer timer = new System.Threading.Timer( timerDelegate,
"nothing" ,1000, 2000);

}

public void TestThread(object state)
{
if (! this.InvokeRequired )
{
base.Refresh();
txtForm.Text = (int.Parse(txtForm.Text) + 1).ToString();
}
else
{
Invoke(new WaitCallback(TestThread), new object[] {state});
}
}
}
}


//-------------------------------
Class1.cs ----------------------------------------
using System;
using System.Threading;


namespace _ThreadTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
public class Class1
{
private static Form1 _frmStatus;
private static TimerCallback timerDelegate;
private static System.Threading.Timer timer;


public Class1()
{
}

[STAThread]
static void Main(string[] args)
{

_frmStatus = new Form1();
_frmStatus.button1.Visible = false;
_frmStatus.Run();

// Create the delegate that invokes methods for the timer.
timerDelegate = new TimerCallback(_frmStatus.TestThread);

// Create a timer that waits one second, then invokes every second.
timer = new System.Threading.Timer( timerDelegate, "nothing" ,1000,
10000);


for(int i = 0; i < 20; i++)
{
//Do some job
Thread.Sleep(1000);
}
//Stop the close the status form. (This will exit the UI thread).
_frmStatus.CloseForm();

//You can do more work here

}
}
}


HTH
B\rgds
100
 
Back
Top