Sends bulk email messages using Tasks Parallel.ForEach.
using System;
using System.Collections.Generic;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Messenger
{
/// <summary>
/// Sends bulk email messages using Tasks Parallel.ForEach.
///
/// Partially taken from:
/// https://talkdotnet.wordpress.com/2014/03/20/sending-bulk-emails-with-tasks-parallel-foreach/
/// </summary>
public class Mailer
{
private readonly SmtpClient[] _smtpClients;
private CancellationTokenSource _canellationTokenSource;
/// <summary>
/// Initializes a new instance of the <see cref="Mailer" /> class.
/// </summary>
/// <param name="smtpClientCount">The number of SMTP connections
/// to create to the server.</param>
public Mailer(int smtpClientCount)
{
SmtpClientCount = smtpClientCount;
_smtpClients = new SmtpClient[smtpClientCount + 1];
CreateSmtpClients();
}
/// <summary>
/// Create "x" number SMTP connections to the server. (recommended 15)
/// You don't want indicate some sort of DoS (Denial-of-service) attack
/// by creating new SMTP connections for every email being sent.
/// </summary>
/// <value>The SMTP client count.</value>
public int SmtpClientCount { get; private set; }
public void Run(List<MailRecipient> recipients)
{
try
{
var parallelOptions = new ParallelOptions();
// Create a cancellation token so you can cancel the task.
_canellationTokenSource = new CancellationTokenSource();
parallelOptions.CancellationToken = _canellationTokenSource.Token;
// Manage the MaxDegreeOfParallelism instead of .NET Managing this.
// We dont need 500 threads spawning for this.
parallelOptions.MaxDegreeOfParallelism = System.Environment.ProcessorCount * 2;
try
{
Parallel.ForEach(recipients, parallelOptions, (MailRecipient recipient) =>
{
try
{
var mailMessage = new MailMessage();
mailMessage.To.Add(recipient.EmailAddress);
mailMessage.Subject = recipient.Subject;
mailMessage.Body = recipient.MessageBody;
mailMessage.BodyEncoding = Encoding.UTF8;
mailMessage.IsBodyHtml = true;
mailMessage.Priority = MailPriority.Normal;
SendMessage(mailMessage);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
});
}
catch (OperationCanceledException ex)
{
//User has cancelled this request.
Console.WriteLine(ex.Message);
}
}
finally
{
DisposeSmtpClients();
}
}
public void CancelRun()
{
_canellationTokenSource.Cancel();
}
private void SendMessage(MailMessage msg)
{
bool isLocked = false;
while (!isLocked)
{
// Keep looping through all smtp client connections
// until one becomes available:
for (int i = 0; i <= SmtpClientCount; i++)
{
if (System.Threading.Monitor.TryEnter(_smtpClients[i]))
{
try
{
_smtpClients[i].Send(msg);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
System.Threading.Monitor.Exit(_smtpClients[i]);
}
isLocked = true;
// TODO: might not be correct. Was : Exit For
break;
}
}
// make sure CPU doesn't ramp up to 100%:
System.Threading.Thread.Sleep(1);
}
if (msg != null)
{
msg.Dispose();
}
}
private void CreateSmtpClients()
{
for (int i = 0; i <= SmtpClientCount; i++)
{
var smtpClient = new SmtpClient();
_smtpClients[i] = smtpClient;
}
}
private void DisposeSmtpClients()
{
for (int i = 0; i <= SmtpClientCount; i++)
{
_smtpClients[i].Dispose();
}
}
}
public class MailRecipient
{
public string UserName { get; set; }
public string Subject { get; set; }
public string EmailAddress { get; set; }
public string MessageBody { get; set; }
}
}