Threading and returning values

D

David

Hi all,

I am very new to threading. I don't really yet fully understand it. However,
I have this...

TcpListener listener = new TcpListener(IPAddress.Loopback, 26);
listener.Start();
while (true)
{
SMTP.SMTPServer handler = new
SMTP.SMTPServer(listener.AcceptTcpClient());
Thread thread = new System.Threading.Thread(new
ThreadStart(handler.Run));
thread.Start();

string email = handler.EmailContent;
}

I am not sure what is actually going on... When I reach the SMTP.SMTPServer
handler line, the program appears to halt until I get a connection. I am not
too concerned about that, but I do need to return something back out...

When I get a connection, it is a general SMTP server. At the end of the DATA
command (or on QUIT) I want to return the EmailContent back up to the
calling program. (The EmailContent is a property that gets populated during
the DATA command).

What is actually happening is that when a connection is made, the Thread
thread and the thread.start() lines are run, then the string email line,
before any of the SMTP commands are run.


I am using the code from here
http://forums.whirlpool.net.au/forum-replies-archive.cfm/654973.html but
have extracted the code fragment above into the form load, to start the
server when the form is run. (I have extended the sample to respond properly
as an SMTP server.)

So, my questions are:
1. In the while(true) loop, why does the program appear to stop?
2. What is the above code fragment actually doing?
3. How do I get the content of the email out, so that I can process it
further?

Thanks.

--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available
 
B

Ben Voigt [C++ MVP]

David said:
Hi all,

I am very new to threading. I don't really yet fully understand it.
However, I have this...

TcpListener listener = new TcpListener(IPAddress.Loopback,
26); listener.Start();
while (true)
{
SMTP.SMTPServer handler = new
SMTP.SMTPServer(listener.AcceptTcpClient());
Thread thread = new System.Threading.Thread(new
ThreadStart(handler.Run));
thread.Start();

string email = handler.EmailContent;
}

I am not sure what is actually going on... When I reach the
SMTP.SMTPServer handler line, the program appears to halt until I get
a connection. I am not too concerned about that, but I do need to
return something back out...

Peter has done a good job of describing what your program actually does.
Here's another suggestion for a fix:

Write your own thread procedure which will:

// note, you have to pass listener to your thread now somehow, you can use a
member variable for this.
SMTP.SMTPServer handler = new SMTP.SMTPServer(listener.AcceptTcpClient());
spawn another thread to keep listening
handler.Run();
enqueue handler.EmailContent
return, ending the thread

Now all your main thread needs to do is

Create the listener and store it where the workers can get to it
Create the first worker thread
DO
Wait on the queue, processing each EmailContent however you like.
LOOP

That queue can take a couple forms. If you have a GUI, a good way is for
the worker thread to call MainForm.BeginInvoke(HandlerDelegate,
handler.EmailContent); and the main thread just runs Application.Run().

If you are in a service, you could use an AutoResetEvent object that the
worker sets after enqueuing the data, and wait on that event in the main
service procedure. Be sure to process the queue until it's empty every time
the main procedure wakes up, because it's possible that multiple workers
finish processing before the main procedure wakes up.
 
D

David

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.

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?

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)

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.)


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.


--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available
Peter Duniho said:
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
 
D

David

Hi again,

I have now got my SMTP server apparently running correctly now, apart from
one of the SMTP servers that is required to connect doesn't appear to be
following SMTP (i.e. send EHLO, a response of 500 should cause the remote
server to then try HELO, but it isn't, it is just quitting.)


Here is my updated code. I have removed all the comments to make it easier
to read.

Basically, inside the DoWork delegate, I have my while(true) loop running
again. I have also reverted back to using the EmailContent property, as I
saw no way to return the text from the end of the Run statement.

I need to do some more testing, but early indications are that it will work
fine. I am probably not coding this very well (apart from using Peter's
original sample code).




System.Net.IPAddress ipaddress = System.Net.IPAddress.Any;
int PortNo = Convert.ToInt32(ConfigurationManager.AppSettings["SMTPPort"]);


BackgroundWorker bw = new BackgroundWorker();


bw.DoWork += delegate(object sender, DoWorkEventArgs e)
{

TcpListener listener = new TcpListener(ipaddress, PortNo);
listener.Start();

while (true)
{
SMTP.SMTPServer server = new
SMTP.SMTPServer(listener.AcceptTcpClient());
Thread thread = new System.Threading.Thread(new
ThreadStart(server.Run));
thread.Start();

string result = server.EmailContent;

e.Result = result;
}

};


bw.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs
e)
{

string MailContent = (string)e.Result;

Pop3Message myMessage = new Pop3Message(MailContent);

string Subject = myMessage.Subject;

if (myMessage.IsMultipart)
{
HandleMail(myMessage.MultipartEnumerator);
}

HandleAttachments();

};


bw.RunWorkerAsync();


--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available
David said:
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.

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?

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)

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.)


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.


--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available
Peter Duniho said:
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
 
D

David

Humppphhhh!!!

That didn't work. The e.Result is not coming out of the loop. However, the
loop does keep the mail server alive, but not quite what I want.

I could simply put the Pop3Message component as a reference to the SMTP
component and call it from within there. However, this is not what should be
happening. (The Pop3Message component actually gets specific objects out of
the email, such as headers, body, subject, from, to and attachments, it the
is the attachments that I am after, and I am also using POP3 to collect
email from within the service, so already have that working and I don't
really want to embed it into SMTP. The SMTP should be purely the transport,
my service should do the de-compose of the message).

I will look into the BeginAcceptTCPClient. However, any further help would
be appreciated.

--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available


David said:
Hi again,

I have now got my SMTP server apparently running correctly now, apart from
one of the SMTP servers that is required to connect doesn't appear to be
following SMTP (i.e. send EHLO, a response of 500 should cause the remote
server to then try HELO, but it isn't, it is just quitting.)


Here is my updated code. I have removed all the comments to make it easier
to read.

Basically, inside the DoWork delegate, I have my while(true) loop running
again. I have also reverted back to using the EmailContent property, as I
saw no way to return the text from the end of the Run statement.

I need to do some more testing, but early indications are that it will
work fine. I am probably not coding this very well (apart from using
Peter's original sample code).




System.Net.IPAddress ipaddress = System.Net.IPAddress.Any;
int PortNo =
Convert.ToInt32(ConfigurationManager.AppSettings["SMTPPort"]);


BackgroundWorker bw = new BackgroundWorker();


bw.DoWork += delegate(object sender, DoWorkEventArgs e)
{

TcpListener listener = new TcpListener(ipaddress, PortNo);
listener.Start();

while (true)
{
SMTP.SMTPServer server = new
SMTP.SMTPServer(listener.AcceptTcpClient());
Thread thread = new System.Threading.Thread(new
ThreadStart(server.Run));
thread.Start();

string result = server.EmailContent;

e.Result = result;
}

};


bw.RunWorkerCompleted += delegate(object sender,
RunWorkerCompletedEventArgs e)
{

string MailContent = (string)e.Result;

Pop3Message myMessage = new Pop3Message(MailContent);

string Subject = myMessage.Subject;

if (myMessage.IsMultipart)
{
HandleMail(myMessage.MultipartEnumerator);
}

HandleAttachments();

};


bw.RunWorkerAsync();


--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available
David said:
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.

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?

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)

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.)


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.


--
Best regards,
Dave Colliver.
http://www.AshfieldFOCUS.com
~~
http://www.FOCUSPortals.com - Local franchises available
Peter Duniho said:
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
 

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

Similar Threads


Top