У меня есть следующий словарь:

Dictionary<long, ChangeLogProcess> _changeLogProcesses = 
    new Dictionary<long, ChangeLogProcess>();

У меня есть метод, который пытается получить следующий процесс журнала изменений в словаре определенного статуса (если нет элементов с определенным статусом, он возвращает null):

 var changeLogProcesses =
     from entry in _changeLogProcesses
     where (entry.Value.Status == status)
     select entry.Value;

 changeLogProcess = changeLogProcesses.FirstOrDefault<ChangeLogProcess>();

Однако во время выполнения он вызывает исключение переполнения стека во время запроса linq? Я провел множество тестов, чтобы убедиться, что в списке есть элементы и т. Д., Но проблема не устранена?

Стоит отметить, что этот метод является частью службы, работающей в многопоточной среде. Вышеупомянутый запрос linq (и весь доступ к нему, например добавление / удаление элементов в список или изменение статуса элементов в списке) заключены в блокировки записи ReaderWriterLockSlim. Опять же, я тщательно отладил его, чтобы убедиться, что к списку в любое время обращается только один поток.

Что может вызвать переполнение стека, в отличие от некоторых других возможных ошибок, таких как изменение списка во время запроса? (опять же, у меня есть только один поток, обращающийся к списку одновременно)

РЕДАКТИРОВАТЬ: по запросу код получателя и установщика:

 public ChangeLogProcessStatus Status 
 {
    get { return _status; }
    set
    {
        //more that one place can initiate a retry now, so retry count is handled in the property setter
        if (PreviousStatus <= ChangeLogProcessStatus.Waiting && value >= ChangeLogProcessStatus.RetryWaiting)
        {
            this.ChangeLog.Tries++;

            //If it's retry waiting, remove this last service machine from the 
            //list so it can try it again because it's not an error
            if (value == ChangeLogProcessStatus.RetryWaiting && _previousServiceMachineIds.Count > 0)
            {   
                _previousServiceMachineIds.RemoveAt(_previousServiceMachineIds.Count() - 1);
            }
        }

        PreviousStatus = _status;
        _status = value;

    }
}      

ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ - я удалил предыдущие примеры, поскольку в этом коде проблемы не было.

Оказывается, это было в другой части приложения, и найти фрагмент рекурсии было очень сложно. Это совпадение, что ошибка переполнения стека возникла во время запроса linq, который в результате вызывал 420000+ раз рекурсивно.

Все ответы ниже были полезными и указывали на правильный путь к поиску проблемы в многопоточных приложениях, однако в первом ответе определенно подчеркивалась рекурсия как проблема, которая и оказалась тем, чем она оказалась (хотя это и не было одним из средств доступа к свойствам, как казалось очевидным).

Еще раз спасибо

Благодарность

2
Mike Tours 12 Фев 2010 в 18:35
Пожалуйста, опубликуйте ChangeLogProcess - особенно установщик Status
 – 
tanascius
12 Фев 2010 в 18:38
4
Интересно, что в наши дни «переполнение стека» превратилось в «переполнение стека». ;)
 – 
AnthonyWJones
12 Фев 2010 в 18:39
Могло ли это застрять где-нибудь в петле? Например, когда он итеративно вызывает метод.
 – 
Tony The Lion
12 Фев 2010 в 18:39
2
Как выглядит трассировка стека в точке исключения?
 – 
Eric Lippert
12 Фев 2010 в 18:40
Опять же, нам нужно больше кода (например, PreviousStatus или ChangeLog.Tries). В принципе, ответ Тванфоссона все еще может быть правильным ^^
 – 
tanascius
12 Фев 2010 в 18:47

3 ответа

Лучший ответ

Проверьте свойство класса ChangeLogProcess, чтобы убедиться, что оно не ссылается на самого себя. Это, я думаю, наиболее вероятная причина исключения переполнения стека в данном случае.

Пример:

 private ChangeLogStatus status;
 public ChangeLogStatus Status
 {
      get { return this.Status; }  // instead of this.status
      set { this.status = value }
 }

Другой возможный вариант - проверка статуса на равенство. Вы переопределили Equals () для ChangeLogStatus? Проверьте там, чтобы убедиться, что у вас нет кода, ссылающегося на себя (или, по крайней мере, способа прекратить рекурсию).

5
tvanfosson 12 Фев 2010 в 19:05
Не могли бы вы уточнить случайно?
 – 
Mike Tours
12 Фев 2010 в 18:39
Получатель статуса выглядит следующим образом: get {return _status; }
 – 
Mike Tours
12 Фев 2010 в 18:40
Это, вероятно, самый распространенный источник SO (или метод A, вызывающий B, который вызывает A).
 – 
user1228
12 Фев 2010 в 18:54
Вы имеете в виду "this.Value" - если это так, я понимаю, но, насколько я понимаю, в моем примере все в порядке? Я опубликовал дополнительные свойства, используемые в сеттере в этом примере.
 – 
Mike Tours
12 Фев 2010 в 18:56
Я не смотрел достаточно внимательно, Value - это свойство KeyValuePair, и я уверен, что все в порядке. Возможно, вы захотите посмотреть определение Equals для ChangeLogStatus для ссылок на себя. Это действительно единственное другое место, о котором я могу думать.
 – 
tvanfosson
12 Фев 2010 в 19:07

Я заметил, что некоторые коллекции ведут себя очень плохо при одновременном касании двух потоков.

Фактически вы позволяете нескольким потокам касаться коллекции одновременно. RWLS позволяет нескольким потокам получать доступ во время операций чтения и блокировать операцию записи. Таким образом, два потока могут одновременно читать, т. Е. Касаться коллекции.

Я предлагаю заменить RWLS на простой lock () и попытаться воспроизвести переполнение стека.

Если это решит вашу проблему, я бы посоветовал подумать о переходе на 4.0, чтобы воспользоваться преимуществами одновременных коллекций. В качестве альтернативы вы можете создать свою собственную поточно-ориентированную коллекцию cough Refctor cough , чтобы лучше контролировать эту ситуацию.

2
user1228user1228 12 Фев 2010 в 18:46
Спасибо, Уилл. Как уже упоминалось, даже во время чтения, такого как rthis, я использую ЗАПИСЬ БЛОКИРОВКИ RWLS. Я также добавил код отладки (увеличение и уменьшение числа), чтобы гарантировать, что над словарем одновременно не выполняется больше одной операции. Спасибо.
 – 
Mike Tours
12 Фев 2010 в 18:49
1
Предположения - ужасные вещи. По крайней мере, они получают меня каждый раз. Можете ли вы воспроизвести в однопоточном тесте? Попробуйте заблокировать игру для развлечения и возможной прибыли.
 – 
user1228
12 Фев 2010 в 18:53
Я могу подтвердить, что это работает в однопоточном примере. Знаете ли вы, что самое худшее в этом примере: если я добавлю console.Writeline ("debugsomething"); чуть выше запроса linq .. он работает. Что абсурдно, ИМО ....
 – 
Mike Tours
12 Фев 2010 в 18:58

http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

Безопасность потоков

Public static (общий в Visual Basic) члены этого типа потокобезопасны. Потокобезопасность любых членов экземпляра не гарантируется.

Словарь <(Of <(TKey, TValue>)>) может поддерживать несколько читателей одновременно, пока коллекция не модифицируется. Даже в этом случае перечисление через коллекцию по сути не потокобезопасный процедура. В том редком случае, когда перечисление борется с записью доступа, коллекция должна быть заблокирован в течение всего перечисление. Чтобы разрешить сбор доступ к нескольким потокам для чтение и письмо, вы должны реализовать собственную синхронизацию.

Акцент мой.

1
Amy B 12 Фев 2010 в 19:20
Я только что добавил полную реализацию метода ... Синхронизация осуществляется с помощью тонкой блокировки WRITE блокировки чтения и записи. Этого достаточно?
 – 
Mike Tours
12 Фев 2010 в 19:29
Коллекция должна быть заблокирована на протяжении всего перечисления.
 – 
Amy B
12 Фев 2010 в 19:39