Обнаружение неожиданного блокированного поведения?
Я пишу программу проверки веб-ссылок и сталкиваюсь с поведением Interlocked, которое не могу объяснить. Во-первых, вот сокращенная версия кода:
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));
}
}
}
В выводе отладки я вижу такие вещи:
- LinkChecker: проверена ссылка "http://serverfault.com", осталось: 1
- LinkChecker: проверена ссылка "http://superuser.com", осталось: 0
- LinkChecker: проверена ссылка 'http://stackru.com', осталось: -1
Я не понимаю, почему (в некоторых кодах работает) _remainingLinks
становится отрицательным. Это также имеет побочный эффект, вызывающий AllLinksChecked
событие от увольнения слишком рано. (Кстати, код выше содержит только те места, которые _remainingLinks
тронут.)
Что я делаю неправильно?
2 ответа
Я собираюсь выйти на конечности и предложить LinkChecker
запускает более одного события для вызова Check()
, Если не считать этого, я не вижу, как значение может стать отрицательным.
Ваш AllLinksChecked
логика определенно неверна, так как счетчик может идти 0->1
, 1->0
, 0->1
, 1->0
, 0->1
, 1->0
и, таким образом, достичь нуля несколько раз.
Но я не понимаю, как счет может стать отрицательным. Это единственные места, которые _remainingLinks
появляется в вашем коде?
Первую проблему можно решить, просто удалив код приращения из ProcessLink
и имея ProcessLinks
инициализировать счет links.Count
перед началом цикла:
Interlocked.Exchange(ref _remainingLinks, links.Count)`
links
аргумент не пишется из других потоков, пока ProcessLinks
работает, не так ли?