| We're gearing
up to begin sending out our email newsletter here at Eggheadcafe.com
and so we've been experimenting with different ways to send out a large
number of emails to subscribers. Since the SMTP class does
not expose an asynchronous version of the SmtpMail.Send(Message) method,
(e.g. BeginSend / EndSend) we either have to use a commercial or open
source component that does, or roll our own.
Fortunately the Framework comes to our rescue here with
callback delegates and the BeginInvoke method, which (unfortunately)
is only documented by Microsoft for Windows Forms controls (making developers
think that that's the only place it's actually available to use). To
add to the potential confusion, the BeginInvoke / EndInvoke methods for
Windows Forms controls are actually a completely different animal from
what we
are
discussing
here
with AsyncCallbacks.
Suffice to say that the methods we want to use here are in fact available
anytime you use a delegate.
Are you confused now? I certainly was!
Here’s
a class with properties to make it useful for sending out html
emails asynchronously. First the code, then the discussion:
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Web.Mail;
using System.Diagnostics;
using System.IO;
namespace eggheadcafe.util.smtp
{
/// <summary>
/// USAGE:
/// EmailUtil eu = new EmailUtil();
/// eu.HtmlFilePath=@"c:\temp\file.htm";
/// eu.Subject ="test message";
/// eu.SmtpServer="mail.myserver.com";
/// eu.FromEmail="you@yourserver.com";
/// (loop here through your datatable etc. of email recipients ---)
/// eu.SendEmailAsync("daddy longlegs", "dlonglegs@yahoo.com");
/// eu.SendEmailAsync("Mama Bear", "mamabear@mindless.com");
/// eu.SendEmailAsync("Bill Clinton", "billy@clinton.org");
/// eu.SendEmailAsync("Jack Spratt", "sprattsksy@yayhey.com");
/// </summary>
public class EmailUtil
{
private string htmlFilePath;
public string HtmlFilePath
{
get
{
return htmlFilePath;
}
set
{
htmlFilePath=value;
}
}
private string subject;
public string Subject
{
get
{
return subject;
}
set
{
subject=value;
}
}
private string smtpServer;
public string SmtpServer
{
get
{
return smtpServer;
}
set
{
smtpServer=value;
}
}
private string fromEmail;
public string FromEmail
{
get
{
return fromEmail;
}
set
{
fromEmail=value;
}
}
public string strBody=String.Empty;
public string SendEmail(string name, string emailAddress)
{
if(strBody==String.Empty)
{
try
{
using (StreamReader reader=new StreamReader(this.HtmlFilePath))
{
strBody = reader.ReadToEnd();
}
}
catch(Exception ex)
{
throw new Exception("error reading HTML File" + ex.Message);
}
}
try
{
MailMessage Message = new MailMessage();
Message.BodyFormat= MailFormat.Html;
Message.To = emailAddress;
Message.From = this.FromEmail;
Message.Subject = this.Subject;
Message.Body = strBody;
SmtpMail.SmtpServer = this.SmtpServer;
SmtpMail.Send(Message);
}
catch(System.Web.HttpException ehttp)
{
throw new Exception("Send error" + ehttp.Message);
}
return "sent" + name;
}
public delegate string SendEmailDelegate(string name, string emailAddress);
public void GetResultsOnCallback(IAsyncResult ar)
{
SendEmailDelegate del = (SendEmailDelegate)
((AsyncResult)ar).AsyncDelegate;
try
{
string result;
result = del.EndInvoke(ar);
Debug.WriteLine("\nOn CallBack: result is " + result);
}
catch (Exception ex)
{
Debug.WriteLine("\nOn CallBack, problem occurred: " + ex.Message); }
}
public string SendEmailAsync(string name,string emailAddress)
{
SendEmailDelegate dc = new SendEmailDelegate(this.SendEmail);
AsyncCallback cb = new AsyncCallback(this.GetResultsOnCallback);
IAsyncResult ar = dc.BeginInvoke(name,emailAddress, cb, null);
return "ok";
}
} // end class EmailUtil
}
|
So we start out with some handy properties (SmtpServer, etc.) which
can be set on the class prior to calling any methods, for convenience
purposes. Then we have our SendEmail method, which looks like any normal
synchronous method - no difference at all.
Now comes the interesting stuff: First, we need a delegate:
public delegate
string SendEmailDelegate(string name, string emailAddress);
Then finally we need the callback
method:
public void GetResultsOnCallback(IAsyncResult
ar)
{
SendEmailDelegate del = (SendEmailDelegate) ((AsyncResult)ar).AsyncDelegate;
string result;
result = del.EndInvoke(ar);
}
Notice the delegate must have the same signature as the method. Based
on this delegate syntax, we can now call the BeginInvoke() and EndInvoke()
methods:
public string
SendEmailAsync(string name,string emailAddress)
{
SendEmailDelegate
dc = new SendEmailDelegate(this.SendEmail);
AsyncCallback cb = new AsyncCallback(this.GetResultsOnCallback);
IAsyncResult ar = dc.BeginInvoke(name,emailAddress, cb, null);
return "ok";
}
Note that the delegate calls BeginInvoke / EndInvoke use a Framework – managed
ThreadPool under the hood--
so you don’t get 4,000 runaway threads. When a thread completes, it is
automatically made available to be re-used for the next BeginInvoke
call in your loop. So your default ThreadPool size of 25 threads is as high
as you get. You could experminent with different ThreadPool sizes, but the
main
thing is you want to avoid just creating thousands of threads, one for each
email, and having your box crash under the load.
To use this class, for example, with a DataSet containing a table of
names and email addresses of our subscribes, we might use code like the
following:
DataTable dt = ds.Tables[0];
EmailUtil
eu = new EmailUtil();
eu.HtmlFilePath=@"c:\temp\file.htm";
eu.Subject
="Newsletter for you!";
eu.SmtpServer="mail.myserver.com";
eu.FromEmail="you@yourserver.com";
for (int i =0; i<dt.Rows.count;i++)
{
eu.SendEmailAsync(dt.Rows[0].Items["name"].ToString(),
dt.Rows[0].Items["email"].ToString());
}
The really cool thing about this exercise is that you can use the technique
above to wire up almost any class / method that doesn't explicitly expose
an
asynchronous
version,
to be
successfully
called asynchronously. Use with caution, please don't send out spam,
folks.
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  | Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|