#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. По-видимому, я так усердно работал над этим проектом, что у меня текут глаза, и я больше не могу читать код или свои собственные сообщения об отладке. Спасибо!