Обнаружение неожиданного блокированного поведения?

Я пишу программу проверки веб-ссылок и сталкиваюсь с поведением 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 работает, не так ли?

Другие вопросы по тегам