Control.Invoke...Lots of controls and need help starting...

T

trint

Ok,
I start my thread job:

Thread t = new Thread(new
ThreadStart(invoicePrintingLongRunningCodeThread));
t.IsBackground = true;
t.Start();

There are lots of calls to controls and many happen in function calls
from invoicePrintingLongRunningCodeThread. I need just an example in
getting started with my thread job with Invoking so that this will
always run and not fizzle out by not doing it right.
Here is the thread job (it's not that long):

private void invoicePrintingLongRunningCodeThread ()
{
Hide();

//listBox1.Items.Add("Network : " + SystemInformation.Network );

if (button7.Text == "&Start Printing Invoices")
{
//temp name test
button7.Text = "&Invoices Are Being Processed";
///Run in separate thread

// ThreadStart myThreadDelegate = new
ThreadStart(ThreadFunction1.getOneAtATime);
// Thread thr1 = new Thread(myThreadDelegate);
// thr1.Start();
bool couNTer1 = false;
do
{
// Form1 findThread = new Form1();
label15.Text = ("0" + " Invoices Left to Print");
loadInvoiceContainer();
label15.Text = (Convert.ToString(Class1.cI) + " Invoices Left to
Print");
button7.Text = "&Watching for New Invoices";
if(Class1.cI > 0)
{
gotoForeach();
}
} while (couNTer1 == false);
}
else
{
// button7.Text = "&Start Printing Invoices";
PrinterBreaks = "BreaksOn";
// Thread tid1 = new Thread(new
ThreadStart(findThread.getOneAtATime ) );
// tid1.Abort();
}
}

Any help is appreciated.
Thanks,
Trint
 
G

Guest

As you are running on a non-UI thread you have to push any UI requests back
to the main UI thread. The InvokeRequired property on a Control specifies
whether an invoke is required or not. It really depends on the underlying
implementation of a Control whether or not it must be invoked but the general
"safe" rule is that you must interact with a control only on the main UI
thread. Some controls may handle this automatically in which case your code
would work just fine. For others the following skeleton implementation is
what you want:

delegate void StringDelegate ( Control target, string parm );
void SetText ( Control target, string text )
{
if (target.InvokeRequired)
target.Invoke(new StringDelegate(SetText), new object[] { target, text
});
else
target.Text = text;
}

The code basically determines if it is running on the UI thread. If it
isn't then in invokes the same method on the UI thread (Control.Invoke). If
it is on the UI thread then it simply sets the property. By changing the
delegate, Invoke arguments and the else-block you can modify this to work
with any property or method. Note that this is a blocking call. Use
BeginInvoke/EndInvoke to make it async.

Most controls don't implement this automatically for performance reasons but
I wouldn't be surprised if they don't migrate that way. For library code
that you write to encapsulate this stuff you should probably do this
automatically for your callers. For example instead of exposing a progress
bar on the status bar of your main form you should instead expose a method
from your main form to interact with the progress bar. In this case the
above logic should be encapsulated in the method you write.

As an aside I would also recommend that you minimize your cross-thread calls
and UI interaction. I'd recommend building an object that contains the
required UI changes and then invoke a method to update the UI with the object
contents when the thread is done (unless this is progress information). This
is especially true of list-style controls like ListBox and ListView. It
depends on how you want your UI to react though.

Hope this helps,
Michael Taylor - 9/16/05
 
J

Jon Skeet

There are lots of calls to controls and many happen in function calls
from invoicePrintingLongRunningCodeThread. I need just an example in
getting started with my thread job with Invoking so that this will
always run and not fizzle out by not doing it right.
Here is the thread job (it's not that long):

Again, I'd suggest looking at the sample code I linked to before:
http://www.pobox.com/~skeet/csharp/threads/winforms.shtml

That gives an example of updating two labels, which is the kind of
thing you're
doing here. Basically, look through your code and identify everything
which
updates the UI - and make sure that runs in a thread-safe manner as per
the article.

Jon
 
T

trint

Michael,
When it reaches gotoForeach();, from there a statusbar is constantly
updated and so are several label controls. I will have to somehow
address each call to a control correctly throughout the code.
From the snipit I have given, it doesn't show the calls to update the
status bar or the text labels. Here is an another example of where I
need help in protecting this code:
public void PrinterStatusNPI7FC6D2()
{

string trayvalue = string.Empty;
///Tray 3
RFC1157.Mgmt mib = new RFC1157.Mgmt();
Snmp.ManagerSession sess=new
Snmp.ManagerSession(Class1.selectLJPrinter,"public");
Snmp.ManagerItem mi=new
Snmp.ManagerItem(sess,mib.OID("1.3.6.1.2.1.25.3.5.1.1.1"));
// listBox1.Items.Add("Tray 3".ToString());
// listBox1.Items.Add(mi.Value.ToString());
trayvalue = Convert.ToString(mi.Value);
label7.Text = string.Format("Printer Status: {0}",trayvalue);
if (trayvalue == "0")
{
PrinterBreaks = "BreaksOn";
Tray3 frmText3 = new Tray3();
frmText3.ShowDialog();
}

}

one more:

statusBarPanel2.Reset();
statusBarPanel2.Step();
statusBarPanel2.ProgressPosition = ((long)(Class1.toNerlOng));

Really would appreciate your help.
Thanks,
Trint
 
T

trint

Jon,

I am working with your example at this very moment.
How would I be able to use Control.Invoke with updating the status bar
here:

statusBarPanel2.Reset();
statusBarPanel2.Step();
statusBarPanel2.ProgressPosition = ((long)(Class1.toNerlOng));

Thanks,
Trint
 
J

Jon Skeet

Pass the "long" value to a method which does the reset, step and
progress position setting all within the same Control.Invoke call.

Jon
 
G

Guest

In your case I'd recommend creating a method on your form that accepts a
custom object (structure) that contains the label values and the progress bar
values. I'd then invoke this method in the secondary thread. This optimizes
your performance by making only a single cross-thread call per loop instead
of 3. Here is some sample code:

struct UpdateData
{
public UpdateData ( string status, long progress, params string[] items )
{
Status = status;
Progress = progress;
Items = items;
}
public string Status;
public string[] Items;
public long Progress;
}

class MyForm : Form
{
delegate void UpdateStatusDelegate ( UpdateData data );
void UpdateStatus ( UpdateData data )
{
if (InvokeRequired)
Invoke(new UpdateStatusDelegate(UpdateStatus), new object[] { data
});
else
{
foreach(string item in data.Items)
listbox1.Items.Add(item);

label7.Text = data.Status;

statusBarPanel2.Reset();
statusBarPanel2.Step();
statusBarPanel2.ProgressPosition = data.Progress;
};
}
}

public void PrinterStatusNPI7FC6D2()
{
string trayvalue = string.Empty;
///Tray 3
RFC1157.Mgmt mib = new RFC1157.Mgmt();
Snmp.ManagerSession sess=new
Snmp.ManagerSession(Class1.selectLJPrinter,"public");
Snmp.ManagerItem mi=new
Snmp.ManagerItem(sess,mib.OID("1.3.6.1.2.1.25.3.5.1.1.1"));

UpdateData data = new UpdateData();
string trayvalue = mi.Value.ToString();
data.Items = new string[] { "Tray 3", trayvalue };
data.Status = String.Concat("Printer Status: ", trayvalue);
f (trayvalue == "0")
{
//This could be bad
PrinterBreaks = "BreaksOn";
Tray3 frmText3 = new Tray3();
frmText3.ShowDialog();
}

//Maybe this is separate??
data.Progress = (long)Class1.toNerlOng;

//UI update
frmObj.UpdateStatus(data);
}

One thing that I'd be concerned about is that form creation in your
secondary thread. That is generally a bad idea because you are creating a
new form on a background thread. I'd try to do that some other way instead.

Michael Taylor - 9/16/05
 
T

trint

Michael,
I don't know if this matters, but the frmText3.ShowDialog(); form is
like a message box (in the way it works) that just says "Tray 3 is out
of paper" and one button "Problem solved" that exits
frmText3.ShowDialog();.
If I had time at this point, I would certainly rather find some other
way of informing the user other than using controls.
Thanks,
Trint
 
G

Guest

MessageBox-style dialogs are "OK" and won't cause a crash normally however
you are somewhat limiting yourself. If you were to move away from
Thread.Start and instead go with the ThreadPool (with all its advantages)
then the dialog would cause a problem and potentially crash the app because
the ThreadPool threads are limited and your thread will block until the user
reacts. If your thread was started multiple times you could drain the
ThreadPool. Of course this may be just what you want. One thing that I have
done in the past in these cases is add a status box to my UI where I send
messages like this. It is better than a status panel because the information
isn't lost the next time I update the status. Instead the user sees that
there was a problem. Again it is a matter of requirements. So for now you
should be alright but you might want to look at an alternative in the future.

Michael Taylor - 9/16/05
 
T

trint

Michael,
I got an error with frmObj.UpdateStatus(data); and had to drop the
frmObj. to make it just UpdateStatus(data);. How would I define
frmObj?
Thanks,
Trint
 
G

Guest

I used frmObj to represent your form object. If your thread method is inside
the form then you can just leave it off. If your thread method is in a
separate class or a static method then you'll need to pass the form object as
a parameter. If it compiled without it then you must have the thread method
inside the form class.

Michael Taylor - 9/16/05
 
T

trint

Michael,
How can I, if I find a certain condition, pause the thread until the
user, let's say puts paper into tray 4?
Thanks,
Trint
 
G

Guest

There are a variety of ways. A thread "pauses" whenever it makes a blocking
call so you really only need to put a blocking call into the thread. In the
case of your MessageBox thing that was discussed earlier that is effectively
pausing the thread until the user reacts. In your case you could detect the
condition in the thread (or however you do it), display the message box,
check the condition again. Put this in a loop like so:

while (Needs paper)
{
MessageBox.Show("Add paper.");
}

If you don't want to continually prompt the user and their is an external
way (i.e. another thread) to know that the paper has been added then you
should instead use an event. The thread will call ManualResetEvent.WaitOne()
to block until the event is raised. In the other thread once the condition
has been met then set the event. Your paused thread then releases. Note
that in this case you should periodically wake up your thread to check for
other events (such as termination requests) otherwise your thread will never
release until the condition is added.

Michael Taylor - 9/16/05
 
T

trint

Michael,
Would it be better to write the updates to an html file and do away
with the controls?
Thanks,
Trint
 
T

trint

Michael,
Would it be better to do away with the controls and use streamwriter to
a file that notifies an html page that can be used as a "printer
status" update?
It seems like control.invoke was not really meant to be used unless
absolutely neccessary? Maybe I'm wrong.
Thanks,
Trint
 
G

Guest

Control.Invoke was added to support cross-thread communication with the UI
and therefore is commonly used in multithread apps. As far as the HTML page
thing goes you could do that as well. Whatever is easiest and best for your
app.

Michael Taylor - 9/16/05
 
T

trint

Michael,
This one :

while (Needs paper)
{
MessageBox.Show("Add paper.");

}

was an excellent idea because the MessageBox is created by the
longrunning thread and not the UI. Right?
Thanks,
Trint
 
G

Guest

MessageBox is a direct call into the Win32 API so it works without going
through your UI. However I'll again caution you that doing UI stuff in a
worker thread is normally a bad idea and could cause problems down the road.
If possible the long running thread should simply raise an async event that
the main form can react to. The secondary thread should then either block
(with appropriate timeout) or simply terminate until the user says they have
resolved the issue (or your code detects that they have). I also wouldn't do
this in the ThreadPool because you are going to stall a thread.

Michael Taylor - 9/19/05
 

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