我们有一个相当大的应用程序,该应用程序的一部分需要每天发送大约30万封电子邮件。
在.net中执行此操作非常慢,为了解决这个问题,我们确实使用了线程,这会使整个服务器崩溃。
我确定有更大的应用发送更多电子邮件。
在合理的时间范围内发送大量电子邮件的最佳方式是什么。
这是代码
第1部分主题发送
System.Threading.Tasks.Task.Run(() =>
{
System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();
Utility.sendTemplatedEmail(model.email, "noreply@fakedomain.com", "New Jobs!", fullHtml: fullHtml, checkUnsubscribed: false, smtpClient: smtp);
smtp.Dispose();
lock (syncObject)
{
emailCounter++;
writer.WriteLine(string.Format("sent and smtp client closed: {0} -- email number: {1}", DateTime.UtcNow.ToString("dd/MM/yyyy hh:mm:ss.fff tt"), emailCounter.ToString()));
writer.Flush();
if (emailCounter == numberOfGroupedMatches)
{
writer.Close();
System.IO.File.Move(Server.MapPath("~/Temp/") + "emailLog.txt", Server.MapPath("~/Temp/") + "emailLog-Finished.txt");
}
}
});
第2部分实际发送
fullHtml = (fullHtml == null ? ViewToString("~/View/Shared/_DocumentTemplate.cshtml", controllerContext, new emailModel { body = body, subject = subject }) : fullHtml);
messageMail.Body = fullHtml;
System.Net.Mail.SmtpClient smtp = (smtpClient == null ? new System.Net.Mail.SmtpClient() : smtpClient);
smtp.Send(messageMail);
答案 0 :(得分:1)
假设您使用的是内置的.Net System.Net.Mail.SmtpClient库,我建议您考虑制作自己的或使用其他第三方库。我还处理从.Net Windows服务生成的大量电子邮件......当我停止让微软为我发送邮件时,我的里程大幅增加。
以下类是我用作"第一选择",它非常轻量级......但是可能会也可能不会与您发送到的邮件服务器一起使用。在我的情况下,我将所有的SMTP流量传递给SendGrid,因此它节省了大量的CPU周期。原作者:http://www.codeproject.com/Articles/9637/SMTP-MailMessage-done-right
使用System; 使用System.Collections; 使用System.Diagnostics; 使用System.IO; 使用System.Net; 使用System.Net.Sockets; 使用System.Text; 使用System.Text.RegularExpressions; 使用System.Net.Mail;
命名空间General.Utilities.Mail { 公共类SMTPSendRawMIME { #region构造函数 public SMTPSendRawMIME() {
}
#endregion
//The following methods are KNOWN to be flawed... they work only with some SMTP Servers... not many....
//On the other hand, they run very fast... so they are worth using when possible like with SendGrid
#region Send Raw Mime
#region Static Send Methods
public static void SendEmail(SmtpClient Server, string FromEmail, string ToEmail, byte[] MIMEMessage) //bool SSL,
{
SMTPSendRawMIME objMailClient = new SMTPSendRawMIME();
objMailClient.Send(Server, FromEmail, ToEmail, MIMEMessage);
}
public static void SendEmail(General.Utilities.Mail.MailTools.MailServerTypes enuUseServerType, string FromEmail, string ToEmail, byte[] MIMEMessage) //bool SSL,
{
SmtpClient objServer = General.Utilities.Mail.MailTools.GetMailServer(enuUseServerType);
SMTPSendRawMIME objMailClient = new SMTPSendRawMIME();
objMailClient.Send(objServer, FromEmail, ToEmail, MIMEMessage);
}
#endregion
public void Send(SmtpClient Server, string FromEmail, string ToEmail, byte[] MIMEMessage) //bool SSL,
{
if (Server.Credentials != null)
{
//if (SSL)
//SendSSL(Server.Host, Server.Port, ((NetworkCredential)Server.Credentials).UserName, ((NetworkCredential)Server.Credentials).Password, FromEmail, ToEmail, MIMEMessage);
//else
Send(Server.Host, Server.Port, ((NetworkCredential)Server.Credentials).UserName, ((NetworkCredential)Server.Credentials).Password, FromEmail, ToEmail, MIMEMessage);
}
else
{
SendToOpenRelay(Server.Host, Server.Port, FromEmail, ToEmail, MIMEMessage);
}
}
#region BASE Send
/// <summary>
/// Sends the message via a socket connection to an SMTP relay host.
/// </summary>
/// <param name="hostname">Friendly-name or IP address of SMTP relay host</param>
/// <param name="port">Port on which to connect to SMTP relay host</param>
/// <param name="username"></param>
/// <param name="password"></param>
public void Send(string HostName, int Port, string UserName, string Password, string FromEmail, string ToEmail, byte[] MIMEMessage)
{
const int bufsize = 1000;
TcpClient smtp;
NetworkStream ns;
int cb, startOfBlock;
byte[] recv = new byte[bufsize];
byte[] data;
string message, block;
try
{
smtp = new TcpClient(HostName, Port);
ns = smtp.GetStream();
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
message = "EHLO\r\n";
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
}
catch(Exception ex)
{
throw new Exception(string.Format("Unable to establish SMTP session with {0}:{1}", HostName, Port), ex);
}
try
{
//figure out the line containing 250-AUTH
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
startOfBlock = message.IndexOf("250-AUTH");
block = message.Substring(startOfBlock, message.IndexOf("\n", startOfBlock) - startOfBlock);
//check the auth protocols
if (-1 == block.IndexOf("LOGIN"))
throw new Exception("Mailhost does not support LOGIN authentication");
message = "AUTH LOGIN\r\n";
//System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("334"))
throw new Exception(string.Format("Unexpected reply to AUTH LOGIN:\n{0}", message));
message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(UserName)));
//System.Diagnostics.Debug.WriteLine(message, "Client (username)");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("334"))
throw new Exception(string.Format("Unexpected reply to username:\n{0}", message));
message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(Password)));
//System.Diagnostics.Debug.WriteLine(message, "Client (password)");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (message.StartsWith("535"))
throw new Exception("Authentication unsuccessful");
if (!message.StartsWith("2"))
throw new Exception(string.Format("Unexpected reply to password:\n{0}", message));
message = string.Format("MAIL FROM: <{0}>\r\n", FromEmail);
//System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("250"))
throw new Exception(string.Format("Unexpected reply to MAIL FROM:\n{0}", message));
message = string.Format("RCPT TO: <{0}>\r\n", ToEmail);
string strRcptToMessage = message;
//System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("250"))
throw new Exception(string.Format("Unexpected reply to RCPT TO:\n{0}", message + " (" + strRcptToMessage + ")"));
message = "DATA\r\n";
//System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("354"))
throw new Exception(string.Format("Unexpected reply to DATA:\n{0}", message));
//message = payload + "\r\n.\r\n";
//data = Encoding.ASCII.GetBytes(Encoding.UTF8.GetString(MIMEMessage) + "\r\n.\r\n");
data = MIMEMessage;
ns.Write(data, 0, data.Length);
message = "\r\n.\r\n";
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
//System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("250"))
throw new Exception(string.Format("Unexpected reply to end of data marker (\\r\\n.\\r\\n):\n{0}", message));
message = "QUIT\r\n";
//System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
}
catch (Exception ex)
{
General.Utilities.Debugging.Report.SendError("SMTP Communication Error", ex);
throw new Exception(string.Format("SMTP Communication Error: {0}", message), ex);
}
finally
{
if (null != smtp) smtp.Close();
}
}
#endregion
#region BASE SendSSL
/// <summary>
/// Sends the message via a socket connection to an SMTP relay host.
/// </summary>
/// <param name="hostname">Friendly-name or IP address of SMTP relay host</param>
/// <param name="port">Port on which to connect to SMTP relay host</param>
/// <param name="username"></param>
/// <param name="password"></param>
private void SendSSL(string HostName, int Port, string UserName, string Password, string FromEmail, string ToEmail, byte[] MIMEMessage)
{
//This Doesn't Work Yet... :(
const int bufsize = 1000;
TcpClient smtp;
System.Net.Security.SslStream ns;
int cb, startOfBlock;
byte[] recv = new byte[bufsize];
byte[] data;
string message, block;
try
{
smtp = new TcpClient(HostName, Port);
ns = new System.Net.Security.SslStream(smtp.GetStream(), true);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
message = "EHLO\r\n";
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
}
catch(Exception ex)
{
throw new Exception(string.Format("Unable to establish SMTP session with {0}:{1}", HostName, Port), ex);
}
try
{
//figure out the line containing 250-AUTH
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
startOfBlock = message.IndexOf("250-AUTH");
block = message.Substring(startOfBlock, message.IndexOf("\n", startOfBlock) - startOfBlock);
//check the auth protocols
if (-1 == block.IndexOf("LOGIN"))
throw new Exception("Mailhost does not support LOGIN authentication");
message = "AUTH LOGIN\r\n";
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("334"))
throw new Exception(string.Format("Unexpected reply to AUTH LOGIN:\n{0}", message));
message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(UserName)));
System.Diagnostics.Debug.WriteLine(message, "Client (username)");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("334"))
throw new Exception(string.Format("Unexpected reply to username:\n{0}", message));
message = string.Format("{0}\r\n", Convert.ToBase64String(Encoding.ASCII.GetBytes(Password)));
System.Diagnostics.Debug.WriteLine(message, "Client (password)");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (message.StartsWith("535"))
throw new Exception("Authentication unsuccessful");
if (!message.StartsWith("2"))
throw new Exception(string.Format("Unexpected reply to password:\n{0}", message));
message = string.Format("MAIL FROM: <{0}>\r\n", FromEmail);
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("250"))
throw new Exception(string.Format("Unexpected reply to MAIL FROM:\n{0}", message));
message = string.Format("RCPT TO: <{0}>\r\n", ToEmail);
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("250"))
throw new Exception(string.Format("Unexpected reply to RCPT TO:\n{0}", message));
message = "DATA\r\n";
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("354"))
throw new Exception(string.Format("Unexpected reply to DATA:\n{0}", message));
data = MIMEMessage;
ns.Write(data, 0, data.Length);
message = "\r\n.\r\n";
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
clearBuf(recv);
cb = ns.Read(recv, 0, recv.Length);
message = Encoding.ASCII.GetString(recv);
System.Diagnostics.Debug.WriteLine(message, "Server");
if (!message.StartsWith("250"))
throw new Exception(string.Format("Unexpected reply to end of data marker (\\r\\n.\\r\\n):\n{0}", message));
message = "QUIT\r\n";
System.Diagnostics.Debug.WriteLine(message, "Client");
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
}
catch (Exception ex)
{
throw new Exception(string.Format("SMTP Communication Error: {0}", message), ex);
}
finally
{
if (null != smtp) smtp.Close();
}
}
#endregion
#region BASE SendToOpenRelay
/// <summary>
/// Sends the message via a socket connection to an SMTP relay host.
/// </summary>
/// <param name="hostname">Friendly-name or IP address of SMTP relay host</param>
/// <param name="port">Port on which to connect to SMTP relay host</param>
public void SendToOpenRelay(string HostName, int Port, string FromEmail, string ToEmail, byte[] MIMEMessage)
{
TcpClient smtp;
NetworkStream ns;
int cb;
byte[] recv = new byte[256];
byte[] data;
string message;
try
{
smtp = new TcpClient(HostName, Port);
ns = smtp.GetStream();
message = "HELO\r\n";
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
cb = ns.Read(recv, 0, recv.Length);
System.Diagnostics.Debug.WriteLine(Encoding.ASCII.GetString(recv), "Server");
}
catch(Exception ex)
{
throw new Exception(string.Format("Unable to establish SMTP session with {0}:{1}", HostName, Port), ex);
}
try
{
message = string.Format("MAIL FROM: <{0}>\r\n", FromEmail);
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
cb = ns.Read(recv, 0, recv.Length);
if (!Convert.ToString(recv).StartsWith("501"))
throw new Exception("Malformed sender address");
if (!Convert.ToString(recv).StartsWith("250"))
throw new Exception(string.Format("SMTP host responded incorrectly to MAIL FROM:, response was:\n{0}", Convert.ToString(recv)));
message = string.Format("RCPT TO: <{0}>\r\n", ToEmail);
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
cb = ns.Read(recv, 0, recv.Length);
if (Convert.ToString(recv).StartsWith("501"))
throw new Exception("Malformed recipient address");
if (!Convert.ToString(recv).StartsWith("250"))
throw new Exception(string.Format("SMTP host responded incorrectly to RCPT TO:, response was:\n{0}", Convert.ToString(recv)));
message = "DATA\r\n";
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
cb = ns.Read(recv, 0, recv.Length);
if (!Convert.ToString(recv).StartsWith("354"))
throw new Exception(string.Format("SMTP host responded incorrectly to DATA, response was:\n{0}", Convert.ToString(recv)));
data = MIMEMessage;
ns.Write(data, 0, data.Length);
message = "\r\n.\r\n";
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
cb = ns.Read(recv, 0, recv.Length);
message = "QUIT\r\n";
data = Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
}
catch (Exception ex)
{
throw new Exception(string.Format("SMTP Communication Error: {0}", message), ex);
}
finally
{
if (null != smtp) smtp.Close();
}
}
#endregion
#region clearBuf
private void clearBuf(byte[] buf)
{
for (int i = 0; i < buf.Length; i++) buf[i] = 0;
}
#endregion
#endregion
}
}
答案 1 :(得分:1)
使用线程来执行异步IO是不必要的(大部分时间),并且会导致程序无法正常扩展,因为您现在可能会注意到这一点。
首先,我不确定你为什么要使用线程,如果你最终必须在共同对象上lock
,这通常是代码气味的标志,我建议你再考虑一下。 / p>
一个好主意是将SmtpClient.SendMailAsync
与C#5的async-await
功能组合使用:
public async Task SendSmtpMailAsync()
{
fullHtml = (fullHtml == null ? ViewToString("~/View/Shared/_DocumentTemplate.cshtml", controllerContext, new emailModel { body = body, subject = subject }) : fullHtml);
messageMail.Body = fullHtml;
System.Net.Mail.SmtpClient smtp = smtpClient ?? new SmtpClient();
await smtp.SendMailAsync(messageMail);
}
您可以在第一段代码中使用它,如下所示:
using (System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient())
{
await Utility.SendTemplatedMailAsync(model.email, "noreply@fakedomain.com", "New Jobs!", fullHtml: fullHtml, checkUnsubscribed: false, smtpClient: smtp);
}
// Note i removed the lock from this piece of code. If you have to execute multiple async methods concurrently (using Task.WhenAll), then maybe it should be re-added
emailCounter++;
writer.WriteLine(string.Format("sent and smtp client closed: {0} -- email number: {1}", DateTime.UtcNow.ToString("dd/MM/yyyy hh:mm:ss.fff tt"), emailCounter.ToString()));
writer.Flush();
if (emailCounter == numberOfGroupedMatches)
{
writer.Close();
System.IO.File.Move(Server.MapPath("~/Temp/") + "emailLog.txt", Server.MapPath("~/Temp/") + "emailLog-Finished.txt");
}