Сталкиваетесь с неожиданным взаимосвязанным поведением?

#c# #.net #multithreading #interlocked

#c# #.net #многопоточность #заблокировано

Вопрос:

Я пишу программу проверки веб-ссылок и сталкиваюсь с поведением с блокировкой, которое я не могу объяснить. Во-первых, вот сокращенная версия кода:

 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));
        }
    }
}
  

Что я вижу в выходных данных отладки, так это такие вещи, как:

  • Средство проверки ссылок: проверена ссылка ‘http://serverfault.com’, осталось: 1
  • Средство проверки ссылок: проверена ссылка ‘http://superuser.com’, осталось: 0
  • Средство проверки ссылок: проверена ссылка ‘http://stackoverflow.com’, осталось: -1

Я не понимаю, почему (в некоторых запусках кода) _remainingLinks становится отрицательным. Это также имеет побочный эффект, заключающийся в том, что AllLinksChecked событие запускается слишком рано. (Кстати, приведенный выше код содержит единственные места, к которым _remainingLinks прикасаются.)

Что я делаю не так?

Комментарии:

1. Как ни странно, я никогда не использовал Interlocked. Читайте — в идеале, просто фиксируйте возвращаемое значение Decrement . Тем не менее, все еще читаю…

2. Это LinkChecker ваш собственный класс? Возможно ли, что при проверке одной ссылки запускается более одного события?

3. Кстати, у вас действительно так много ссылок, что 32 бит недостаточно?

4. Интересно, нужно ли нам видеть другой класс, особенно event

5. @MarcGravell использует long , потому что заблокирован. Чтение возвращает единицу, хотя да, мне нужно только int . Я не могу опубликовать другой класс, потому что это IP-адрес моей компании, но я проверю его на наличие нескольких запусков событий.

Ответ №1:

Ваша 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 выполнения, не так ли?

Комментарии:

1. 1 за то, что вы обратили внимание на логическую ошибку, я почти наверняка пропустил бы это.

Ответ №2:

Я собираюсь рискнуть и предположить, что LinkChecker запускает более одного события для вызова Check() . За исключением этого, я не вижу, как значение может стать отрицательным.

Комментарии:

1. Я думаю, это было бы правдоподобно, если бы это был рекурсивный паук.

2. По-видимому, я так усердно работал над этим проектом, что у меня текут глаза, и я больше не могу читать код или свои собственные сообщения об отладке. Спасибо!