This is a kind of post where I share with you what I learned while solving a problem. Likely you might think of 3 better ways to do this. If so, please share. I love learning.
The overall goal is to create a Pop3 service utilizing an off-the-shelf Pop3 library to poll one or more email accounts and push all emails found into a business workflow. Here we go.
Separate the code from library specificity
In the past I worked on a similar piece of software and we got burned by tight coupling to the Pop3 library. My goal this time around is to completely isolate the Pop3Service from the library that ultimately does the POP3 protocol icky-ness. I first wrote a wrapper around the Pop3 library creating a class that simply returns emails.
public interface IEmailGateway
{
IEmailMessage[] GetAll();
}
Doing something repeatedly
Next up we need a service class for polling a list of IEmailGateways at a specified interval. To accomplish this I use the Threading.Timer class which lets you specify a callback to be called on a recurring basis from the thread pool. Excellent. We don’t have to worry about ookie thread management and endless sleeping while loops.
public class Pop3Service
{
…
public void Start(Action<IEmailMessage> receiveEmailAction)
{
_receiveEmailAction = receiveEmailAction;
IsStarted = true;
_getEmailTimer = new Timer(TimedEmailRetrieval, null, _waitSpanBeforeInitialEmailRetrieval, _getEmailInterval);
}
public void TimedEmailRetrieval(object notUsed)
{
if (ShouldCheckEmail())
{
RetrieveEmailsFromRepositories();
}
}
But not concurrently
My testing quickly ran into an issue when retrieving emails. If you set the Timer interval to be so short that next timer fires before the current one completes, multiple threads will be hitting the Pop3 server. Besides being bad behavior I found out that my integration email server (hMailServer) didn’t let like this.
To solve this I added some synchronization and updated email retrieval to do nothing when email is already being retrieved.
private void RetrieveEmailsFromRepositories()
{
lock (_locker)
{
_isCheckingEmail = true;
foreach (var emailRepository in _emailGateways)
{
foreach (var emailMessage in emailRepository.GetAll())
{
_receiveEmailAction(emailMessage);
}
}
_isCheckingEmail = false;
}
}
private bool ShouldCheckEmail()
{
return IsStarted && !_isCheckingEmail;
}
Testing for potential concurrency problems is tricky. From what I can see the worst case scenario for this code is that back to back email retrievals get executed.