Re: Threading and returning values
- From: "Peter Duniho" <NpOeStPeAdM@xxxxxxxxxxxxxxxx>
- Date: Fri, 03 Apr 2009 11:27:49 -0700
On Fri, 03 Apr 2009 07:32:39 -0700, David
<david.colliver.NEWS@xxxxxxxxxxxxxxxxxxxxxxx> wrote:
Hi Peter,
Thanks for your help. I have sort of got it working, but not fully.
This SMTP sits inside a service, so no button click. However, I have put the
code (and all the comments so that I can reference later) into a function
off service start.
I don't have much experience with services. But my recollection is that a
service has a similar requirement that you not block its primary thread,
and this would include (I think) not putting a call to a method that never
returns into the service start logic.
If you're using the BackgroundWorker or similar, then this should be
fine. But I just wanted to mention that, in case it's relevant.
The other point is that with BackgroundWorker, if there's not a
SynchronizationContext available that it can use to inject the
RunWorkerCompleted event execution on, it will simply raise the event on
the background thread. For your service, I'm pretty sure this means that
the event _won't_ be raised on the same thread on which the
BackgroundWorker was created. This may or may not be important; assuming
you don't need to synchronize access to any data structures the original
thread may be using, it shouldn't be an issue. But if you're relying on
that behavior as a way of synchronization between the threads, then that
won't work in a service, not without the SynchronizationContext (and
again, I'm not that experienced with services, but my recollection is that
the main service thread doesn't have a SynchronizationContext).
The service is actually meant to either collect email via POP3, or have
email delivered to it via SMTP. I already have the POP3 side working, just
got to get the SMTP side working properly.
So, when it starts, I run the SMTP bw thread that you have so kindly
demonstrated. The problem (one of which you have highlighted) is that once I
have received one email, I get no more coming through. I have attempted to
remove listener.stop() but that doesn't work. Should this be in a
while(true) loop?
That depends. The simplest approach would be to deal with only one
connection at a time. If that's the approach you want to take, then
yes...you can remove the "listener.Stop()", and put the whole thing from
the "Accept..." to the "Run" inside an infinite loop.
If you do that, however, I would not bother with the BackgroundWorker, and
instead just use a regular Thread and conventional synchronization
techniques (especially given that without a SynchronizationContext, as
provided for a Forms or WPF main thread, BackgroundWorker doesn't really
add much over a regular Thread).
If you want to be able to handle multiple connections at once, then you
have a couple of approaches. One is to have one thread that initalizes
the listener, and then infinitely loops calling "Accept...". With each
successfully accepted client, it would then begin another thread to handle
that client.
The other approach is to use the asynchronous "Begin..."/"End..."
pattern. I personally find this the most useful and elegant, but it can
be challenging for people new to threading to immediately grasp. The
general idea is that instead of managing a thread where you can make a
call that blocks without holding up some other thread, instead you call a
method that won't block (i.e., one that starts with the word "Begin"). In
that call, you pass a delegate referencing a method to call when the
operation completes, and .NET will automatically execute that method on a
different thread at the appropriate time.
In the usual pattern, each time the callback is called, it calls the
"End..." method to retrieve the results, processes whatever small chunk of
data has shown up, and then reenables the waiting by calling a "Begin..."
method again. In the case of the client-accept callback, for example, the
processing would actually be to call the "BeginRead..." from the client
NetworkStream, and the reenabling would be to call "BeginAccept..." again
on the TcpListener.
In that way, you don't need to explicitly manage any of the threading.
Because of that, as I said, I find it to actually be a simpler approach,
even if the asynchronous aspect may _seem_ more complicated to the
beginner. Another advantage the asynchronous approach has is that when
supported by the OS, it uses an i/o construct known as "i/o completion
ports" that allows for the most efficient use of threads. But, unless you
anticipate having to deal with thousands or more clients simultaneously,
this wouldn't be an issue for you.
Also, for some odd reason, I am not getting anything in the e.Result(). Let
me explain how I have the EmailContent working...
In the code that I have used from that sample link, I have put in a new read
only public property called EmailContent. Near to the end of the case
"DATA": I have populated _EmailContent (which is what EmailContent uses to
return) with the 'message'. (I have done that just before the
Console.Error.Writeline)
If you assign the backing field for "EmailContent" before assigning the
value of the property to "e.Result" in the DoWork event handler, and you
make that assignment before returning from the DoWork event handler, then
it should in fact be there when it's inspected in the RunWorkerCompleted
event handler.
If that's not working as expected, then without an actual
concise-but-complete code sample that reliably illustrates what you're
doing, I can't comment on why it wouldn't work.
I have put in all the other essential SMTP commands (HELO (and also EHLO)
RSET, NOOP, MAIL, RCPT, QUIT) and moved the client.close into the QUIT.
Whilst drafting this message, I have just changed the run to return a
string, and put the contents of DATA into the string. I am still not
receiving, however, I think there is more to it, because when I send direct
from outlook express to the server, I do get something, but when I send the
message out via the internet back to my machine, it doesn't. (Puzzled.)
One problem with networking code is that there are many points of
potential failure. This is one of the reasons I said that trying to deal
with learning how to implement multiple technologies all at once can be
very challenging. When it doesn't work, it's hard to know for sure
whether it's because you got the threading wrong, or the networking wrong,
or the SMTP protocol wrong (for example). The networking in particular
requires that you solve challenges like getting through proxies,
firewalls, and NAT routers, nevermind any ISP-specific restrictions on the
use of the SMTP protocol (which is often blocked for clients by ISPs,
especially the low-security port 25 version, due to spam-prevention).
On top of all that, you have the additional requirement of trying to do
this in a service.
I would start with a console application. Get some basic network i/o
working in a single-thread implementation. Maybe start with a simple
"echo" server listening on the telnet port, so you can test it with a
telnet client. Then expand that to handle the SMTP-aware functionality
you want, but still in a single-threaded implementation. Once you've got
that working, extend the implementation to be multi-threaded in the way
that you desire (i.e. either one client at a time, or multiple clients at
a time).
Finally, once all that is resolved, then see about incorporating the
functionality into a service.
If you take that approach, then at each stage you will know for sure that
you'd already gotten the previous stage to work, and if there are any
problems, then they must be related to the stage you're working on
presently (assuming no regressions, of course...hopefully you're familiar
with how to avoid that :) ).
What I also did is to create a new public static void SMTPListen function
and moved the thread into it. (Effectively, renamed the static void Main to
SMTPListen and changed the parameters to take the IPAddress.Any and PortNo).
However, if I now use this as a static method, I am then totally lost as to
how to get the EmailContent out. So, in that case, I will ignore the
SMTPListen function I have created until I have more experience with
threads.
Right. How to get data "out" of a thread is entirely dependent on what
you intend to do with it, and what the other parts of your program look
like. In some cases, the thread itself can do all the work necessary, and
no data needs to be delivered to any other thread. If you do need to
deliver data to another thread, how best to do that depends on that other
thread. But a common pattern is the producer/consumer pattern, where
processing threads write to a common, synchronized queue, and one or more
consumer threads read from the queue and do something appropriate with the
data retrieved from it.
In a Forms application, this often might involve the consumer using the
queue to populate some UI element. As Ben alluded to, you could even just
use Control.Invoke() to effectively make the message loop the "queue". In
a service, presumably there's some processing you intend to do with the
data, so the consumer would pull the data from the queue and do the
appropriate thing with it. But of course, in that scenario, you may find
it makes sense to forget about an explicit queue altogether, and just have
the worker thread do the appropriate processing when it's done dealing
with the i/o.
It just all depends.
Pete
.
- References:
- Threading and returning values
- From: David
- Re: Threading and returning values
- From: Peter Duniho
- Re: Threading and returning values
- From: David
- Threading and returning values
- Prev by Date: Re: How can I identified if the running OS a 32bit or 64bit version?
- Next by Date: Online C# turorials
- Previous by thread: Re: Threading and returning values
- Next by thread: Re: Threading and returning values
- Index(es):