I’m writing a web link checker program and encountering behaviour with Interlocked that I can’t explain. First, here’s an abridged version of the code:
public class LinkCheckProcessor
{
private long _remainingLinks;
public event EventHandler<LinksCheckedEventArgs> AllLinksChecked;
private void ProcessLinks(List<Link> links)
{
foreach (Link link in links)
{
ProcessLink(link);
}
}
private void ProcessLink(Link link)
{
var linkChecker = new LinkChecker(link);
linkChecker.LinkChecked += LinkChecked;
Interlocked.Increment(ref _remainingLinks);
#if DEBUG
System.Diagnostics.Debug.WriteLine(String.Format("LinkChecker: Checking link '{0}', remaining: {1}", link, Interlocked.Read(ref _remainingLinks)));
#endif
linkChecker.Check();
}
void LinkChecked(object sender, LinkCheckedEventArgs e)
{
var linkChecker = (LinkChecker)sender;
Interlocked.Decrement(ref _remainingLinks);
#if DEBUG
System.Diagnostics.Debug.WriteLine(String.Format("LinkChecker: Checked link '{0}', remaining: {1}", linkChecker.Link, Interlocked.Read(ref _remainingLinks)));
#endif
if (Interlocked.Read(ref _remainingLinks) == 0)
{
OnAllLinksChecked(new LinksCheckedEventArgs(this.BatchId, this.Web.Url));
}
}
}
What I’m seeing in the debug output are things like:
- LinkChecker: Checked link ‘http://serverfault.com’, remaining: 1
- LinkChecker: Checked link ‘http://superuser.com’, remaining: 0
- LinkChecker: Checked link ‘http://stackoverflow.com’, remaining: -1
I don’t understand why (in some code runs) _remainingLinks is becoming negative. This is also having the side effect of causing the AllLinksChecked event from being fired too early. (By the way, the code above contains the only places that _remainingLinks is touched.)
What am I doing wrong?
I’m going to go out on a limb and propose that
LinkCheckeris firing more than one event for a call toCheck(). Short of this, I can’t see how the value could possibly go negative.