General Question on Structure

S

Steve Ricketts

I'm converting a very large VB6 MDI application to C# that creates one
"informational display" child form and then a varying number of other child
forms all of the same type. The application will make socket connections to
a couple of different servers, do serial port communication, some radio
communication, play audio, and a number of other things. In the VB app,
most of this work was done in the "information display" form. I keep
running into cross threading issues when trying to reference items created
in other threads (I thought I knew what a thread was, but no longer sure!)

So, my question is, would it be better to do a much work as possible in the
main form (MDI) and let the child forms do only the things that are specific
to that form? Would this help the cross threading issues since all child
forms were created by the main form? Or is this just an issue that is going
to be there anyway?

I know this is a broad question but maybe someone has some tips or links to
good descriptions that might help.

Thanks,

Steve
 
P

Peter Duniho

Steve said:
I'm converting a very large VB6 MDI application to C# that creates one
"informational display" child form and then a varying number of other child
forms all of the same type. The application will make socket connections to
a couple of different servers, do serial port communication, some radio
communication, play audio, and a number of other things. In the VB app,
most of this work was done in the "information display" form. I keep
running into cross threading issues when trying to reference items created
in other threads (I thought I knew what a thread was, but no longer sure!)

So, my question is, would it be better to do a much work as possible in the
main form (MDI) and let the child forms do only the things that are specific
to that form? Would this help the cross threading issues since all child
forms were created by the main form? Or is this just an issue that is going
to be there anyway?

Without a concise-but-complete code example, it's difficult to know for
sure what issues you're asking about, and especially what approaches to
solving them you'd implemented and thus are asking if you will continue
to need to do so.

That said: your main form and children are all (or should be) owned by
the same thread, and so there's no difference in terms of the
cross-threading issues inherent in Forms objects whether you use the
main form or children.

In general, you should have a single thread that owns all of your GUI
objects. Then, in any other thread that may do some work and then need
to interact with the GUI objects to present the status or results of
that work to the user, you'll always have to use something like
Control.Invoke() to access the GUI object.
I know this is a broad question but maybe someone has some tips or links to
good descriptions that might help.

Well, there is a wealth of information in the archives of this newsgroup
discussion various threading issues, especially cross-threading issues
related to GUI objects in Forms and WPF (which have this "thread
affinity" issue where the object must be accessed on the thread that
owns it). Google Groups can be used to search the past articles, which
you can read. Hopefully those help fill in some of the details you seem
to be asking about.

Of course, if you have specific questions about particular techniques,
feel free to ask.

Pete
 
S

Steve Ricketts

Thanks, Peter. More helpful than you know. The bit about "single thread
that owns all of your GUI objects" is a great tip. The code is getting
close to 10,000 lines so I'll spare you the details. Both types of child
forms were created by the main mdi form, but I'm doing a lot of work in the
"information display" child (let's call it form A for later) that twiddles
values in the other child forms. That's the part I thought about moving to
the main form.

Where I start to get lost is the idea of a thread. I thought threads were
things I create. I have threads in the code for the socket work and to
grab data to serve the audio, but I'm getting these cross-thread errors in
other places... where I don't expect. For example, in form A I open a new
instance of a form (not a child) as a dialog box.

Form frmKeypad1 = new frmKeypad();

In another method of the same form I tried to close it:

frmKeypad1.Close();

I get the cross-threading error on the close... so what thread is it talking
about?... as I'm writing this, I realized what I'm actually doing is trying
to close frmKeypad from a method that's called by an event,
SerialDataReceivedEventHandler. That event was assigned in the same form
as the event handler method, so is an event another "thread"?

Thanks again for your patience and response,

sr
 
P

Peter Duniho

Steve said:
[...]
Where I start to get lost is the idea of a thread. I thought threads
were things I create. I have threads in the code for the socket work
and to grab data to serve the audio, but I'm getting these cross-thread
errors in other places... where I don't expect. For example, in form A
I open a new instance of a form (not a child) as a dialog box.

Form frmKeypad1 = new frmKeypad();

In another method of the same form I tried to close it:

frmKeypad1.Close();

I get the cross-threading error on the close... so what thread is it
talking about?...as I'm writing this, I realized what I'm actually
doing is trying to close frmKeypad from a method that's called by an
event, SerialDataReceivedEventHandler. That event was assigned in the
same form as the event handler method, so is an event another "thread"?

Not exactly. What you're running into is that various classes
(especially i/o related ones, but not limited to those) may be doing
work using their own threads. I don't know about the SerialPort class,
but other i/o classes use a special kind of thread pool known as the
IOCP thread pool, that is optimized for dealing with i/o.

When you use those i/o classes (for example) in an asynchronous way,
that means those implicit threads are being used to do work, and when
your own code needs to be notified of the completion (which in the
asynchronous case can happen via a callback or event…an event is really
just a fancy kind of callback), that notification occurs in the same
thread that was doing the work.

Usually. An exception would be a class like BackgroundWorker, which is
specifically designed to handle background task processing in a GUI,
thread-affinity-laden environment. That class checks the thread
information when an instance of it is created, and if possible will
always raise its events on the same thread that created it (except for,
in the case of BackgroundWorker, the DoWork event, which of course
necessarily takes place in the background thread).

In the case of the event you're asking about, it's not so much that
events themselves or their handlers imply a new thread (they don't), but
that that particular event is being raised by the worker thread that was
doing the i/o for you, and that worker thread makes no attempt at all to
try to raise the event on any other thread. Remember, raising an event
is usually just as simple as an invocation of a multicast delegate.

It's up to the client code (i.e. your own code, the handler represented
by the delegate subscribed to the event) to marshal the actual
event-handling code over to the correct thread, if necessary (it's
necessary when dealing with GUI objects...there are other scenarios in
which your own code is responding to/handling the event in a way that's
unrelated to the GUI and so could use some other synchronization
mechanism, assuming one's needed at all). And of course, typically
you'd use something like Control.Invoke() for that.

By the way, the Socket class has a very nice (IMHO) asynchronous API
already (Begin/EndReceive(), similar to the other IAsyncResult APIs in
..NET), so while it won't eliminate the issue of the threads, it will at
least allow you to not worry about dealing with the threads. And
because the Socket class's asynchronous API uses the IOCP thread pool,
the i/o operation handling is more efficient (generally only really an
issue for high-volume networked servers, but it's still nice to know
you're taking advantage of it even if you don't need it :) ).

Pete
 
S

Steve Ricketts

Wow, I feel like you should send me an invoice for all this great
information! ;-)

It's in the BackgroundWorker's RunWorkerCompleted event where I was trying
to close the frmKeypad form. It became my friend real quick! ;-) I just
thought using it whenever would keep me out of cross-thread trouble, but if
I understand what you're saying, there's no connection between the thread
that created the form and the threads used by the
SerialDataReceivedEventHandler event... so BackgroundWorker here doesn't
know squat about the serial port thread and I need to find a way to watch
for its completion.

I've got a few of the InvokeRequired's in the code as well for other things
but when I found BackgroundWorker, it seemed like a much more friendly way
of handing the cross-thread issue. So, do I understand that something like
keyPad1.InvokeRequired would be the way to check completion? Just to kind
of recap what's going on:

- User action in child Form A causes information to be sent via sockets to
server
- Information received from server by child Form A opens frmKeypad1
- Serial port event from RF device received by Form A closes frmKeypad1

So, I do something like...

threadSafeKeypadClose(); // close the keypad form
....

delegate void frmKeypadCallback();
private void threadSafeKeypadClose()
{
if (this.InvokeRequired)
{
frmKeypadCallback d = new frmKeypadCallback(frmKeypadClose);
}
else
{
frmKeypadClose();
}
}
private void frmKeypadClose()
{
frmKeypad1.Close();
}

Whew... CheckForIllegalCrossThreadCalls is sure tempting!! ;-)

In regard to the socket class, I am using the Begin/EndConnect and
Begin/EndReceive functions. I thought I knew WinSock inside and out and was
reluctant to go to sockets but I'm pretty happy with them so far. I do a
lot of transmitting audio/video along with data over the sockets and they
seem to do the job very well.

Steve

Peter Duniho said:
Steve said:
[...]
Where I start to get lost is the idea of a thread. I thought threads
were things I create. I have threads in the code for the socket work
and to grab data to serve the audio, but I'm getting these cross-thread
errors in other places... where I don't expect. For example, in form A I
open a new instance of a form (not a child) as a dialog box.

Form frmKeypad1 = new frmKeypad();

In another method of the same form I tried to close it:

frmKeypad1.Close();

I get the cross-threading error on the close... so what thread is it
talking about?...as I'm writing this, I realized what I'm actually doing
is trying to close frmKeypad from a method that's called by an event,
SerialDataReceivedEventHandler. That event was assigned in the same
form as the event handler method, so is an event another "thread"?

Not exactly. What you're running into is that various classes (especially
i/o related ones, but not limited to those) may be doing work using their
own threads. I don't know about the SerialPort class, but other i/o
classes use a special kind of thread pool known as the IOCP thread pool,
that is optimized for dealing with i/o.

When you use those i/o classes (for example) in an asynchronous way, that
means those implicit threads are being used to do work, and when your own
code needs to be notified of the completion (which in the asynchronous
case can happen via a callback or event…an event is really just a fancy
kind of callback), that notification occurs in the same thread that was
doing the work.

Usually. An exception would be a class like BackgroundWorker, which is
specifically designed to handle background task processing in a GUI,
thread-affinity-laden environment. That class checks the thread
information when an instance of it is created, and if possible will always
raise its events on the same thread that created it (except for, in the
case of BackgroundWorker, the DoWork event, which of course necessarily
takes place in the background thread).

In the case of the event you're asking about, it's not so much that events
themselves or their handlers imply a new thread (they don't), but that
that particular event is being raised by the worker thread that was doing
the i/o for you, and that worker thread makes no attempt at all to try to
raise the event on any other thread. Remember, raising an event is
usually just as simple as an invocation of a multicast delegate.

It's up to the client code (i.e. your own code, the handler represented by
the delegate subscribed to the event) to marshal the actual event-handling
code over to the correct thread, if necessary (it's necessary when dealing
with GUI objects...there are other scenarios in which your own code is
responding to/handling the event in a way that's unrelated to the GUI and
so could use some other synchronization mechanism, assuming one's needed
at all). And of course, typically you'd use something like
Control.Invoke() for that.

By the way, the Socket class has a very nice (IMHO) asynchronous API
already (Begin/EndReceive(), similar to the other IAsyncResult APIs in
.NET), so while it won't eliminate the issue of the threads, it will at
least allow you to not worry about dealing with the threads. And because
the Socket class's asynchronous API uses the IOCP thread pool, the i/o
operation handling is more efficient (generally only really an issue for
high-volume networked servers, but it's still nice to know you're taking
advantage of it even if you don't need it :) ).

Pete
 
P

Peter Duniho

Steve said:
Wow, I feel like you should send me an invoice for all this great
information! ;-)

lol...well, if you really want me to. :)
It's in the BackgroundWorker's RunWorkerCompleted event where I was
trying to close the frmKeypad form. It became my friend real quick!
;-) I just thought using it whenever would keep me out of cross-thread
trouble, but if I understand what you're saying, there's no connection
between the thread that created the form and the threads used by the
SerialDataReceivedEventHandler event... so BackgroundWorker here doesn't
know squat about the serial port thread and I need to find a way to
watch for its completion.

Without a concise-but-complete code example, it's hard to know. But I
will point out: BackgroundWorker isn't a panacea; if it's not created on
the same thread that owns the Control instance (which could be a Form
instance) where your event handler is and which you want to access, then
it's not going to marshal the events over to that Control's owning thread.
I've got a few of the InvokeRequired's in the code as well for other
things but when I found BackgroundWorker, it seemed like a much more
friendly way of handing the cross-thread issue. So, do I understand
that something like keyPad1.InvokeRequired would be the way to check
completion?

There's no need to use the InvokeRequired property. Which is not to say
you shouldn't use Invoke() where appropriate. Just that checking the
property is pointless. Here's a longer discussion I wrote up on the topic:
http://msmvps.com/blogs/duniho/arch...chnique-for-using-control-invoke-is-lame.aspx
Just to kind of recap what's going on:

- User action in child Form A causes information to be sent via sockets
to server
- Information received from server by child Form A opens frmKeypad1
- Serial port event from RF device received by Form A closes frmKeypad1

So, I do something like...

threadSafeKeypadClose(); // close the keypad form
....

delegate void frmKeypadCallback();
private void threadSafeKeypadClose()
{
if (this.InvokeRequired)
{
frmKeypadCallback d = new frmKeypadCallback(frmKeypadClose);
}
else
{
frmKeypadClose();
}
}
private void frmKeypadClose()
{
frmKeypad1.Close();
}

Whew... CheckForIllegalCrossThreadCalls is sure tempting!! ;-)

Don't use it.

I don't have time at the moment to review your code snippet or the rest
of your question closely, but I'll take a look later. In the meantime,
just be aware that BackgroundWorker is in fact very useful for dealing
with cross-thread issues, but you have to make sure you create the
BackgroundWorker instance using the correct thread (i.e. the one where
you need the events to be raised, and which owns the Control instance(s)
that you'll be using).
In regard to the socket class, I am using the Begin/EndConnect and
Begin/EndReceive functions. I thought I knew WinSock inside and out and
was reluctant to go to sockets but I'm pretty happy with them so far.
I do a lot of transmitting audio/video along with data over the sockets
and they seem to do the job very well.

The thing I like most about the .NET Socket class (and there's lots I
like about it) is that the async API is easy to use, and automatically
implements IOCP-based communications. There is actually a slightly more
complicated async API (the methods with "Async" in the name) that's even
more efficient, but for most people, the original IAsyncResult-based API
is sufficient, and it works very well IMHO.

Pete
 
P

Peter Duniho

Here's a bit of follow-up, now that I've had time to read your post more
carefully...

Steve said:
[...] there's no connection
between the thread that created the form and the threads used by the
SerialDataReceivedEventHandler event... so BackgroundWorker here doesn't
know squat about the serial port thread and I need to find a way to
watch for its completion.

As I mentioned, this is hard to answer without seeing code. But if you
mean that you are creating an instance of BackgroundWorker in your
SerialDataReceivedEventHandler method, then right...it has no way to
know of the thread that owns your controls, and thus cannot do the
marshaling it normally would.

Depending on what you're actually doing in the BackgroundWorker's DoWork
event handler, one of a variety of options may make sense:

-- you may find that it makes sense to just do the work in the
SerialDataReceivedEventHandler method and then use Control.Invoke() to
get the UI updated

-- alternatively, you might use Control.Invoke() to execute code that
creates the BackgroundWorker and starts the the processing

-- forgo using the BackgroundWorker altogether and handle things
yourself. This might involve using the ThreadPool directory
(QueueUserWorkItem()), or perhaps even writing a producer/consumer
implementation so that you've got a long-lived thread that processes
data delivered in the serial i/o event handler (and in which you then
use Control.Invoke() explicitly).
I've got a few of the InvokeRequired's in the code as well for other
things but when I found BackgroundWorker, it seemed like a much more
friendly way of handing the cross-thread issue. So, do I understand
that something like keyPad1.InvokeRequired would be the way to check
completion? Just to kind of recap what's going on:

- User action in child Form A causes information to be sent via sockets
to server
- Information received from server by child Form A opens frmKeypad1

How does this happen? Is "frmKeypad1" a modal form, or perhaps it's
being created in some code executed via Control.Invoke()? One way or
the other, for that form to work, there has to be a message pump in the
thread that owns it, so assuming you're not having trouble with that,
you've got one. But it's not clear from the description how that
happens in your case.
- Serial port event from RF device received by Form A closes frmKeypad1

So, I do something like...

threadSafeKeypadClose(); // close the keypad form
....

delegate void frmKeypadCallback();
private void threadSafeKeypadClose()
{
if (this.InvokeRequired)
{
frmKeypadCallback d = new frmKeypadCallback(frmKeypadClose);
}
else
{
frmKeypadClose();
}
}
private void frmKeypadClose()
{
frmKeypad1.Close();
}

Per the blog post I linked to previously, I'd change the code above to
look like this:

void threadSafeKeypadClose()
{
frmKeypad1.Invoke((MethodInvoker) delegate { frmKeypad1.Close(); });
}

Much simpler.

Or possibly even just don't bother with the method itself, and just put
the Invoke() statement in the place where you know you need the
cross-thread-safe operation.

Alternatively, if you can put the method in whatever class implements
the "frmKeypad1" object, you could remove the explicit field uses, and
wind up with this:

void threadSafeClose()
{
Invoke((MethodInvoker) delegate { Close(); });
}

Finally, note that wherever I write "Invoke", that could also be
"BeginInvoke". Most of the time, I find Invoke() better, because its
synchronous nature avoids some of the complexity that can affect the
cross-thread invocation. But there are sometimes useful reasons,
especially for performance, to use BeginInvoke() instead. Just take
care to either not use anything in the invocation that needs
synchronizing, or make sure you do the necessary synchronization (the
latter of course could in turn re-introduce the same performance issue
you were trying to avoid by using BeginInvoke() in the first place).

Pete
 
S

Steve Ricketts

Dude... I've developed some amazing systems over the years, but you're
making me want to quit programming!! ;-) Just can't believe all the great
help and advice!

I don’t know if it helps to know what the application is doing, but in
general this is the receive side of a satellite based, interactive distance
learning application for corporations. A classroom of learners at thousands
of locations view the program on a TV. The instructor will ask true/false,
yes/no, multiple choice questions and the learners respond by pushing
buttons on a radio frequency wireless remote control. That remote also has
a mic built-in so they can press a raise hand button and talk to the
presenter. The remote sends PCM audio through the USB/serial port where
this application converts it to GSM and transmits it to the studio over an
IP connection. From there it's uplinked to the satellite and heard by the
other learners all over the country. All responses are captured, graded,
and stored as part of the learner's permanent training history. The issue
we're talking about here is when the learner first logs on and is asked to
press a button on the wireless remote. frmKeypad is displayed to instruct
them to pick up a remote and press a button on it so the system can link
that person to that remote for the duration of the broadcast program,
recording anything they do with it.

BTW, although this started as a satellite based system, it does the same
thing with terrestrial audio/video... just not with quite as many. ;-)

Ok, so where do I start...
But if you mean that you are creating an instance of BackgroundWorker in
your SerialDataReceivedEventHandler method, then right...it has no way to
know of the thread that owns your controls, and thus cannot do the
marshaling it normally would.

SR: Yep, that's what I meant... and it doesn't marshall!
-- you may find that it makes sense to just do the work in the
SerialDataReceivedEventHandler method and then use Control.Invoke() to get
the UI updated

-- alternatively, you might use Control.Invoke() to execute code that
creates the BackgroundWorker and starts the the processing

SR: Ok, I guess I need to read up on Control.Invoke() a little more. You
seem to be using it in a way that's yet a secret to me. I'll do a little
more research on it and maybe do better next time.
-- forgo using the BackgroundWorker altogether and handle things
yourself. This might involve using the ThreadPool directory
(QueueUserWorkItem()), or perhaps even writing a producer/consumer
implementation so that you've got a long-lived thread that processes data
delivered in the serial i/o event handler (and in which you then use
Control.Invoke() explicitly).

SR: Now you're really scaring me... <reaches for bottle of Scotch and
aspirin chaser> Had to go out and read that last paragraph to my wife...
always impresses her that I exchange e-mails with such smart people... I
pretend to know what it's saying!
How does this happen? Is "frmKeypad1" a modal form, or perhaps it's being
created in some code executed via Control.Invoke()? One way or the other,
for that form to work, there has to be a message pump in the thread that
owns it, so assuming you're not having trouble with that, you've got one.
But it's not clear from the description how that happens in your case.

SR: Ok, what's going on is a person has entered their training ID and it's
sent to a server to verify them and get all the information they need for
the session. Once they are validated, frmKeypad is displayed to tell them
to press a button on the wireless remote control (think TV remote only RF)
to establish a link between that person and the wireless remote. So,
everything they do with the remote from here on can be attributed to them.
Once they press a button on the remote and make that connection, frmKeypad
is closed. They also have the option of clicking a checkbox on frmKeypad if
they intend to use the keyboard and mouse to respond. frmKeypad is not a
modal form. It's just a regular form (if there is such a thing anymore)
that has a lot of instructional information on it if the person is using the
system for the first time. It also has that checkbox they can check if they
aren't going to use the wireless remote and want to use the mouse.
Per the blog post I linked to previously, I'd change the code above to
look like this:
void threadSafeKeypadClose()
{
frmKeypad1.Invoke((MethodInvoker) delegate { frmKeypad1.Close(); });
}
Much simpler.

SR: Simpler, Ya think? I picked up the code originally from somewhere off
the web. Your approach is obviously better. I'll certainly use that, but I
also need to look at it a while to make sure I know what you're doing.
Very elegant. Although, I'm wondering why I'd even need the method.
Couldn't I just call frmKeypad1.Invoke((MethodInvoker) delegate {
frmKeypad1.Close(); }); directly from the location I need to close the form
and call it good?
Or possibly even just don't bother with the method itself, and just put
the Invoke() statement in the place where you know you need the
cross-thread-safe operation.

SR: Assuming I know where I need it... that's kind of part of my problem.
Alternatively, if you can put the method in whatever class implements the
"frmKeypad1" object, you could remove the explicit field uses, and wind up
with this:

void threadSafeClose()
{
Invoke((MethodInvoker) delegate { Close(); });
}

Finally, note that wherever I write "Invoke", that could also be
"BeginInvoke". Most of the time, I find Invoke() better, because its
synchronous nature avoids some of the complexity that can affect the
cross-thread invocation. But there are sometimes useful reasons,
especially for performance, to use BeginInvoke() instead. Just take care
to either not use anything in the invocation that needs synchronizing, or
make sure you do the necessary synchronization (the latter of course could
in turn re-introduce the same performance issue you were trying to avoid
by using BeginInvoke() in the first place).

SR: Now, you're just showing off! ;-) I'm not really concerned about
performance here. This is a stand-alone app that's not going to tax even
your dumpster-grade PC's. I think your previous suggestions are excellent
(not to mention relatively understandable) so I'll go with them.

Please pardon the tongue-in-cheek remarks made here... after (I won't event
tell you how) many years, I'm feeling a bit overwhelmed trying to do all the
things this application requires in a pure .net way and learn C# at the same
time. Your advice and your time have been truly appreciated.

I am not worthy... albeit more than grateful.

sr
 
P

Peter Duniho

Steve said:
Dude... I've developed some amazing systems over the years, but you're
making me want to quit programming!! ;-) Just can't believe all the
great help and advice!

lol...well, happy to help. For what it's worth, I'm not giving away
major secret programming juju here; once you've fiddled with the basic
concepts a bit, it'll all seem old hat to you. :)
[...]
But if you mean that you are creating an instance of BackgroundWorker
in your SerialDataReceivedEventHandler method, then right...it has no
way to know of the thread that owns your controls, and thus cannot do
the marshaling it normally would.

SR: Yep, that's what I meant... and it doesn't marshall!

Nope, it wouldn't.
SR: Ok, I guess I need to read up on Control.Invoke() a little more.
You seem to be using it in a way that's yet a secret to me. I'll do a
little more research on it and maybe do better next time.

I'm only aware of one way to use Control.Invoke(), and that's for the
purpose of executing a delegate on the thread that owns the instance of
Control used to call the Invoke() method.

Hopefully the docs for the method can elaborate well enough. If you
still have problems, feel free to ask.
SR: Now you're really scaring me... <reaches for bottle of Scotch and
aspirin chaser> Had to go out and read that last paragraph to my
wife... always impresses her that I exchange e-mails with such smart
people... I pretend to know what it's saying!

First, the paragraph I wrote above actually doesn't make as much sense
as it should, because for some reason I wrote the word "directory"
instead of "class". I have no idea why that happened, and I apologize
for any confusion I might have caused.

Ignoring that goof of mine, one of the wonderful things about jargon is
that it lets ordinary people like me sound like they know something
special to the people who haven't learned the jargon yet. :)

Seriously though, the jargon is important in that it lets us talk about
relatively detailed concepts with only a few words. But, MSDN has
documentation on the ThreadPool class, and there's no shortage of
references on the producer/consumer pattern to be found with Google
(including this one:
http://msdn.microsoft.com/en-us/library/yy12yx1f(VS.80).aspx).

So, I didn't really say anything that special. It's just stuff you can
easily learn about yourself, but written using jargon so the paragraph
is a lot shorter.
[...]
How does this happen? Is "frmKeypad1" a modal form, or perhaps it's
being created in some code executed via Control.Invoke()? One way or
the other, for that form to work, there has to be a message pump in
the thread that owns it, so assuming you're not having trouble with
that, you've got one. But it's not clear from the description how that
happens in your case.

[...] frmKeypad is not a modal form. It's just a regular form (if
there is such a thing anymore)

Okay, so assuming that form behaves as you expect, you must already be
using the main GUI thread to create it. In any case, that means you can
just call "frmKeypad1.Invoke()" to marshal the call to Close() to the
correct thread (or, of course, from within the keypad form's class, just
call "Invoke()").
[...]
Per the blog post I linked to previously, I'd change the code above to
look like this:
void threadSafeKeypadClose()
{
frmKeypad1.Invoke((MethodInvoker) delegate { frmKeypad1.Close(); });
}
Much simpler.

SR: Simpler, Ya think? I picked up the code originally from somewhere
off the web.

Well, for what it's worth, the code you posted is basically the pattern
MSDN is always recommending. As I mention in my own blog post, it's not
like they don't have a good reason for doing it that way. It's just
that I find the newer syntax nicer.
Your approach is obviously better. I'll certainly use
that, but I also need to look at it a while to make sure I know what
you're doing. Very elegant.

The main "trick" in the syntax I use is that there's an anonymous method
in there. That's what the "delegate" keyword declares for you.

You can read more on MSDN about it, but the short version is: everything
in the braces after the "delegate" keyword is a whole new method
declaration, but one without a name. The declaration must be assigned
to a delegate type destination (variable, property, method argument,
etc.), which can then be invoked like any other delegate instance. The
code in the braces does not get executed until that invocation, and it's
executed just as if you'd had a named method that was invoked in the
same way.
Although, I'm wondering why I'd even need
the method. Couldn't I just call frmKeypad1.Invoke((MethodInvoker)
delegate { frmKeypad1.Close(); }); directly from the location I need to
close the form and call it good?

That's exactly what I mean by the following paragraph:
SR: Assuming I know where I need it... that's kind of part of my problem.

Well, you need it anywhere you'd call the method named
"threadSafeClose()". :)

From your description, you really only have one scenario in which this
comes up: the user clicks on a remote that is detected via your serial
i/o event handler. It's the form close operation you do in response to
that which requires the call to Invoke().

Basically, it should be very unusual to be executing code in a thread
other than the correct one and not know it. Code isn't generally just
executed in whatever random thread happens to be handy. It's executed
in the thread that is specifically tied to the context of the code being
executed.

So, for example, your code in the serial i/o event handler is simply
always going to be executed in a thread other than the main GUI thread.
Or at least _potentially_ in some other thread, which is good enough
to require the call to Invoke().

(Above, I say "potentially" because there are some asynchronous classes
that raise events or invoke callbacks on the same thread where the
operation was started if the operation can complete immediately for some
reason…but for the purpose of dealing with the cross-thread issues, you
generally can ignore that possibility).
SR: Now, you're just showing off! ;-) I'm not really concerned about
performance here. This is a stand-alone app that's not going to tax
even your dumpster-grade PC's. I think your previous suggestions are
excellent (not to mention relatively understandable) so I'll go with them.

Well, to be clear: for some i/o situations, performance does matter,
because if you don't process the i/o events fast enough, you can lose
data. Your serial device may well have some form of flow control to
prevent that, but it's not always a given. If you're sure it does, or
you are at least sure that whatever potential performance overhead might
exist isn't going to come close to causing your code to lag behind the
i/o (e.g. your handling of incoming data can be done rapidly enough),
then yes...you can safely ignore the comment about BeginInvoke().
Please pardon the tongue-in-cheek remarks made here... after (I won't
event tell you how) many years, I'm feeling a bit overwhelmed trying to
do all the things this application requires in a pure .net way and learn
C# at the same time. Your advice and your time have been truly
appreciated.

Happy to help...and frankly, if you weren't "worthy", you'd have given
up on the project long ago. It's definitely not for the faint of heart
to try to deal with all that disparate i/o, asynchronous behavior,
multimedia presentation, and a whole new language and framework all at
the same time. :)

Pete
 

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