On Thu, 02 Apr 2009 11:25:51 -0700, David
Hi all,
I am very new to threading. I don't really yet fully understand it.
Trying to learn about threading _and_ figure out the networking API
_and_ doing the SMTP protocol all at the same time, may not be the best
approach in terms of keeping all the information straight.
[...]
So, my questions are:
1. In the while(true) loop, why does the program appear to stop?
Because that thread does stop. AcceptTcpClient() "blocks" (that is,
doesn't return) until a client has attempted to make a connection. And
of course, your "while(true)" never exits, so even once the connection
has been made, all that happens is that loop goes back and waits for
another connection. The method in which the loop is contained will
never return, and so that thread is effectively blocked at that point.
It can continue to do some useful work -- that is, processing connection
requests -- but otherwise it's not going anywhere.
This can be very bad if the thread is in fact expected to do other
useful work. For example, every GUI application requires a dedicated
thread for the purpose of handling the user-interface, where that thread
loops retrieving messages from Windows and dispatching them to the
appropriate handling code. This dispatch is how, for example, your own
code gets to know about button clicks, menu selections, etc. If in
handling one of those events, you never return (for example, you write a
"while(true)" loop in a method, thus ensuring that method never
returns), then the thread that is supposed to be handling the
user-interface never gets to get back to doing that, and the UI just
stops.
The solution is to put code that may need to spend more than an instant
doing work into a different thread, where it can do all that work
without holding up the UI thread.
Caveat: the code that is doing this other work in another thread will
eventually, most likely, need to present the results back to the UI.
But you can't safely access the UI objects from a thread other than the
UI thread. .NET provides the Control.Invoke() method to address this.
With that method, you can specify code to be executed on the UI thread.
The Invoke() method won't return until it's done so.
Note: there exists the BackgroundWorker class to help with this sort of
thing. Instead of calling Control.Invoke() directly, you can instead
subscribe event handlers to the BackgroundWorker class's DoWork and
RunWorkerCompleted events (and ProgressChanged, if desired). The DoWork
event handler gets executed on the background thread, while handlers for
the other two events are automatically executed on the UI thread used to
create the BackgroundWorker.
Note: _all_ of the above relies on extensive use of delegates, which is
the .NET/C# version of a function pointer. Event handlers are a special
kind of delegate, as are thread entry points, and as is the argument to
the Control.Invoke() method. Before you do any kind of threading code
at all, you should make very sure you really understand how to use
delegates. IMHO, you will be even better off if you become familiar
with anonymous methods as well, as they are a special way of creating a
delegate instance that provides a number of useful conveniences.
2. What is the above code fragment actually doing?
It loops indefinitely, creating a new TcpClient with each successful
connection from a client, and then passing that TcpClient to the
constructor of the SMTP.SMTPServer class. With the new instance of
SMTP.SMTPServer, it then creates a new Thread instance, using the
SMTP.SMTPServer.Run() instance method as the entry point for the Thread,
which it then starts. Immediately after starting the thread, it
attempts to retrieve the "EmailContent" from the SMTP.SMTPServer
instance. However, this is most likely going to fail, because the thread
that actually does the work to get that data probably hasn't had a
chance to run yet (calling "Start()" only makes the thread
runnable...the current thread will continue to execute until it yields
or the Windows thread schedule pre-empts it).
3. How do I get the content of the email out, so that I can process it
further?
Your specific goals are not clear. But, assuming you're only dealing
with a single remote endpoint at a time, IMHO the best thing for you to
do is use the BackgroundWorker class to do this work.
I can't tell from the URL you mentioned how "EmailContent" is actually
declared or used. But, assuming the "Run()" method will return when the
given transaction is completed, and "EmailContent" is correctly
initialized to the data you want at that point, something like this
would probably be appropriate:
// Assuming all this happens in response to a button click:
void button1_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
// This statement subscribes an anonymous method to the DoWork
event.
// Note that in the Click event handler, the anonymous method
itself is
// _not_ executed; a delegate instance that refers to the
anonymous method
// is implicitly created and subscribed to the DoWork event, but
that's all
// that happens at this statement.
bw.DoWork += delegate (object sender, DoWorkEventArgs e)
{
// This method will be executed on a background thread.
You don't have
// to worry about the code causing the UI to become
blocked, but you must
// not try to access any UI objects from this code, at
least not without
// using Control.Invoke() to do it.
TcpListener listener = new
TcpListener(IPAddress.Loopback, 26);
listener.Start();
SMTP.SMTPServer server = new
SMTP.SMTPServer(listener.AcceptTcpClient());
listener.Stop();
server.Run();
e.Result = server.EmailContent;
};
// This statement subscribes an anonymous method to the
RunWorkerCompleted
// event. This event is raised by the BackgroundWorker class
when the
// DoWork event handler returns, but unlike the DoWork handler
(which is
// executed on the background thread), this handler is executed
on the same
// UI thread used to create the BackgroundWorker instance in the
first place.
// As with the DoWork handler assignment, all that's actually
happening in the
// Click handler method itself is the creation of a delegate
instance, and
// subscription of that instance to the RunWorkerCompleted
event.
bw.RunWorkerCompleted += delegate (object sender,
RunWorkerCompletedEventArgs e)
{
// This method will be executed on the UI thread, but
not until after
// the previous anonymous method has been executed on a
background thread.
string strResult = (string)e.Result;
// do whatever you want with the result here. this code
is executed on the
// UI thread, so make it quick, but don't worry about
accessing the UI object;
// they can all be used safely in this method.
};
// This statement is what causes the BackgroundWorker to
actually start working.
// It returns immediately, allowing the UI thread to get back to
doing whatever it
// needs to do, but before returning it sets the
BackgroundWorker into motion,
// getting the DoWork handler assigned to a background thread,
where it can begin
// executing.
bw.RunWorkerAsync();
}
The above is a very basic implementation. One particular refinement you
might _eventually_ want to make is to separate the TcpListener logic
from the SMTP server logic, allowing for a single listener to respond to
repeated connections from various remote endpoints, instead of starting
and stopping a listener for each client. And for that purpose, it's my
opinion that using the asynchronous API -- calling
BeginAcceptTcpClient() and EndAcceptTcpClient() -- is the best approach
for that.
But, those refinements are additional complications, that you probably
won't want to try until you have a solid understanding of the more basic
threading issues that are going on here. The above should hopefully
give you enough discomfort that you have something to spend some time
learning, and yet still remain simple enough that it's not too much to
tackle all at once.
Hope that helps.
Pete