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