ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 14.02.15 22:16
Оценка: :)
Здравствуйте!
Я имею для хранения тегов на сайте примерно такую систему:
1) Общий статический класс для доступа к репозиториям. Он соответственно общий для всех юзеров и всех потоков.
    public static class RepositoryManager
    {
        public static TagRepository TagRepository { get; private set; }

        static RepositoryManager()
        {
            TagRepository = new TagRepository();
        }
    }

2) Сам класс репозитория имеет дублирующую функциональность: работает из бд и с кешем. При инициализации загружает данные из бд в кеш. Судя по отладчику все загружается и сохраняется.
    public sealed class TagRepository : BigSqlRepository<TagData>
    {
        private TagCacheCollection CacheCollection { get; set; }

        public TagRepository()
        {
            CacheCollection = new TagCacheCollection(GetList());
        }

        public override TagData GetById(Int64 id)
        {
            return CacheCollection.GetTag(id);
        }
    }

3) Собственно сам класс кеширования
    public class TagCacheCollection
    {
        private ConcurrentDictionary<Int64, TagData> DataDictById { get; set; } 

        public TagCacheCollection(IEnumerable<TagData> initialCollection)
        {
            DataDictById = new ConcurrentDictionary<Int64, TagData>(initialCollection.Select(i =>
                new KeyValuePair<Int64, TagData>(i.Id, i))); /////Вот тут все работает и загружается
        }

        public TagData GetTag(Int64 id)
        {
            return DataDictById[id]; ////Вот тут падает
        }
    }

И собственно возникает такая проблема:
1) Сначала было так:
Несмотря на то, что данные загружаются в DataDictById нормально, при вызове GetTag в DataDictById этих данные не оказывается. То есть DataDictById остается вообще пустой.
Хотя и инициализируется и дергается DataDictById в одном и том же запросе, то есть на теоретические проблемы многопоточности из-за использования статического класса тут скинуть не удастся.
2) Потом неожиданно, после прохождения всего пути отладчиком, все теги закинулись и все стало работать хорошо и правильно. Я пробовал перезагружать студию, перекомпилировать — все работало. Я добавил новые теги. Из бд они опять же брались, отладчик показывал, что взялись все теги, но в GetTag на этот раз оказались только старые теги и оно опять падает.
Собственно вопрос: что за чудеса не виражах такие?
Re: ConcurrentDictionary и потерянные данные
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 15.02.15 09:13
Оценка:
Здравствуйте, 80LevelElf, Вы писали:

Могу предположить, что вот в этом месте:

public TagCacheCollection(IEnumerable<TagData> initialCollection)
{
    DataDictById = new ConcurrentDictionary<Int64, TagData>(initialCollection.Select(i =>
                new KeyValuePair<Int64, TagData>(i.Id, i))); /////Вот тут все работает и загружается
}


данные из БД достаются не всегда/не все/не сразу. А потом, когда Вы мышкой наводите на initialCollection под отладчиком — они извлекаются все и правильно.

Вставьте отладочный код:

public TagCacheCollection(IEnumerable<TagData> initialCollection)
{
    var myActualData = initialCollection.ToList();

    Debug.Assert(/* и здесь проверить, что из БД досталось все, что должно было */);

    DataDictById = new ConcurrentDictionary<Int64, TagData>(myActualData.Select(i =>
                new KeyValuePair<Int64, TagData>(i.Id, i))); 
}
Re: ConcurrentDictionary и потерянные данные
От: tapatoon  
Дата: 15.02.15 09:25
Оценка:
Здравствуйте, 80LevelElf, Вы писали:

LE>Собственно вопрос: что за чудеса не виражах такие?


Если несколько app domain, тогда могут быть чудеса, т.к. в каждом RepositoryManager будет свой, и то наврядли — данные в конструторах должны загрузиться одинаковые.
Попробуй watch в дебаге поставить на RepositoryManager.TagRepository.CacheCollection.DataDictById, будет видно когда изменяется.
Re[2]: ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 15.02.15 13:42
Оценка:
Здравствуйте, tapatoon, Вы писали:

T>Здравствуйте, 80LevelElf, Вы писали:


LE>>Собственно вопрос: что за чудеса не виражах такие?


T>Если несколько app domain, тогда могут быть чудеса, т.к. в каждом RepositoryManager будет свой, и то наврядли — данные в конструторах должны загрузиться одинаковые.

T>Попробуй watch в дебаге поставить на RepositoryManager.TagRepository.CacheCollection.DataDictById, будет видно когда изменяется.

Ну вот простой пример, как все это меняется (предположим мы добавили 1 элемент):
var tagRepository = new TagRepository(); //Тут показывает, что х + 1 элементов
tagRepository.GetById(id); //А вот здесь уже только х элементов
Re[2]: ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 15.02.15 13:45
Оценка: :)
Здравствуйте, scale_tone, Вы писали:

_>Здравствуйте, 80LevelElf, Вы писали:


_>Могу предположить, что вот в этом месте:


_>
_>public TagCacheCollection(IEnumerable<TagData> initialCollection)
_>{
_>    DataDictById = new ConcurrentDictionary<Int64, TagData>(initialCollection.Select(i =>
_>                new KeyValuePair<Int64, TagData>(i.Id, i))); /////Вот тут все работает и загружается
_>}
_>


_>данные из БД достаются не всегда/не все/не сразу. А потом, когда Вы мышкой наводите на initialCollection под отладчиком — они извлекаются все и правильно.


_>Вставьте отладочный код:


_>
_>public TagCacheCollection(IEnumerable<TagData> initialCollection)
_>{
_>    var myActualData = initialCollection.ToList();

_>    Debug.Assert(/* и здесь проверить, что из БД досталось все, что должно было */);

_>    DataDictById = new ConcurrentDictionary<Int64, TagData>(myActualData.Select(i =>
_>                new KeyValuePair<Int64, TagData>(i.Id, i))); 
_>}
_>


Я про это уже думал и проверял таким образом. Проверил еще раз с помощью вашего кода — тоже самое. При загрузке — загружается все, при выборке остаются только старые элементы.
Re[2]: ConcurrentDictionary и потерянные данные
От: samius Россия http://sams-tricks.blogspot.com
Дата: 15.02.15 15:27
Оценка:
Здравствуйте, scale_tone, Вы писали:

_>Могу предположить, что вот в этом месте:


_>
_>public TagCacheCollection(IEnumerable<TagData> initialCollection)
_>{
_>    DataDictById = new ConcurrentDictionary<Int64, TagData>(initialCollection.Select(i =>
_>                new KeyValuePair<Int64, TagData>(i.Id, i))); /////Вот тут все работает и загружается
_>}
_>


_>данные из БД достаются не всегда/не все/не сразу.

Конструктор ConcurrentDictionary втаскивает IEnumerable eager кодом в foreach. Если не втащит — кинет исключение.
Re[3]: ConcurrentDictionary и потерянные данные
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 15.02.15 17:50
Оценка:
Здравствуйте, samius, Вы писали:

_>>данные из БД достаются не всегда/не все/не сразу.

S>Конструктор ConcurrentDictionary втаскивает IEnumerable eager кодом в foreach. Если не втащит — кинет исключение.

Это понятно и очевидно. Предполагалось, что этот самый IEnumerable может при разных обращениях выдавать разные наборы значений.
Re[3]: ConcurrentDictionary и потерянные данные
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 15.02.15 17:55
Оценка:
Здравствуйте, 80LevelElf, Вы писали:

LE>Я про это уже думал и проверял таким образом. Проверил еще раз с помощью вашего кода — тоже самое. При загрузке — загружается все, при выборке остаются только старые элементы.


Ну, тогда нужен весь остальной код (в частности, метода GetList()), а также подробности и содержимое БД.
Re[3]: ConcurrentDictionary и потерянные данные
От: tapatoon  
Дата: 15.02.15 18:14
Оценка:
Здравствуйте, 80LevelElf, Вы писали:

LE>
LE>var tagRepository = new TagRepository(); //Тут показывает, что х + 1 элементов
LE>tagRepository.GetById(id); //А вот здесь уже только х элементов
LE>


Если этот код не в конструкторе RepositoryManager, то вполне возможно проблема в этом — создаётся новый инстанс TagRepository, а по задумке надо использовать RepositoryManager.TagRepository.
А если в конструкторе... Напиши обёртку для колекции, ставь бряки в функциях, которые её изменяют.
Re[3]: ConcurrentDictionary и потерянные данные
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 15.02.15 21:01
Оценка: +2
Здравствуйте, 80LevelElf, Вы писали:

LE>Ну вот простой пример, как все это меняется (предположим мы добавили 1 элемент):

LE>
LE>var tagRepository = new TagRepository(); //Тут показывает, что х + 1 элементов
LE>tagRepository.GetById(id); //А вот здесь уже только х элементов
LE>


Странный у Вас пример. Не согласуется с идеей синглтонности в исходном коде...
Re[4]: ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 16.02.15 11:21
Оценка:
Здравствуйте, scale_tone, Вы писали:

_>Здравствуйте, 80LevelElf, Вы писали:


LE>>Я про это уже думал и проверял таким образом. Проверил еще раз с помощью вашего кода — тоже самое. При загрузке — загружается все, при выборке остаются только старые элементы.


_>Ну, тогда нужен весь остальной код (в частности, метода GetList()), а также подробности и содержимое БД.

GetList у меня реализовывается с помощью Linq2DB, то есть примерно так:
        public virtual List<T> GetList()
        {
            using (var db = new DataBase())
            {
                return db.GetTable<T>().ToList();
            }
        }

Таблица с тегами такая:
http://s7.hostingkartinok.com/uploads/images/2015/02/e67db1873b16a518eb0baa0e511d0c7a.jpg
И вот полная ссылка на код, который вас интересует: https://github.com/80LevelElf/ZaBugrom/tree/master/CommonDAL
Re[4]: ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 16.02.15 11:22
Оценка:
Здравствуйте, scale_tone, Вы писали:

_>Здравствуйте, 80LevelElf, Вы писали:


LE>>Ну вот простой пример, как все это меняется (предположим мы добавили 1 элемент):

LE>>
LE>>var tagRepository = new TagRepository(); //Тут показывает, что х + 1 элементов
LE>>tagRepository.GetById(id); //А вот здесь уже только х элементов
LE>>


_>Странный у Вас пример. Не согласуется с идеей синглтонности в исходном коде...

Ну я же просто пример привел, мол смотрите, даже если в 1 строке создать DAL, а во 2 сразу же использовать, то получается такая фигня.
Re[4]: ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 16.02.15 11:31
Оценка:
Здравствуйте, tapatoon, Вы писали:

T>Здравствуйте, 80LevelElf, Вы писали:


LE>>
LE>>var tagRepository = new TagRepository(); //Тут показывает, что х + 1 элементов
LE>>tagRepository.GetById(id); //А вот здесь уже только х элементов
LE>>


T>Если этот код не в конструкторе RepositoryManager, то вполне возможно проблема в этом — создаётся новый инстанс TagRepository, а по задумке надо использовать RepositoryManager.TagRepository.


Это был просто пример, мол смотрите даже если создать DAL а потом сразу вызвать будет такая фигня.

T>А если в конструкторе... Напиши обёртку для колекции, ставь бряки в функциях, которые её изменяют.


Да я это коллекцию меняю только один раз(не считая инициализации) и то добавляю. Так что в моем коде удалятся там точно ничего не должно.
Если интересно, вот ссылка на полный код: https://github.com/80LevelElf/ZaBugrom/tree/master/CommonDAL
Re: ConcurrentDictionary и потерянные данные
От: 80LevelElf http://80levelelf.com
Дата: 16.02.15 11:47
Оценка: -1
Здравствуйте, 80LevelElf, Вы писали:

LE>Здравствуйте!

LE>Я имею для хранения тегов на сайте примерно такую систему:
LE>1) Общий статический класс для доступа к репозиториям. Он соответственно общий для всех юзеров и всех потоков.
LE>
LE>    public static class RepositoryManager
LE>    {
LE>        public static TagRepository TagRepository { get; private set; }

LE>        static RepositoryManager()
LE>        {
LE>            TagRepository = new TagRepository();
LE>        }
LE>    }
LE>

LE>2) Сам класс репозитория имеет дублирующую функциональность: работает из бд и с кешем. При инициализации загружает данные из бд в кеш. Судя по отладчику все загружается и сохраняется.
LE>
LE>    public sealed class TagRepository : BigSqlRepository<TagData>
LE>    {
LE>        private TagCacheCollection CacheCollection { get; set; }

LE>        public TagRepository()
LE>        {
LE>            CacheCollection = new TagCacheCollection(GetList());
LE>        }

LE>        public override TagData GetById(Int64 id)
LE>        {
LE>            return CacheCollection.GetTag(id);
LE>        }
LE>    }
LE>

LE>3) Собственно сам класс кеширования
LE>
LE>    public class TagCacheCollection
LE>    {
LE>        private ConcurrentDictionary<Int64, TagData> DataDictById { get; set; } 

LE>        public TagCacheCollection(IEnumerable<TagData> initialCollection)
LE>        {
LE>            DataDictById = new ConcurrentDictionary<Int64, TagData>(initialCollection.Select(i =>
LE>                new KeyValuePair<Int64, TagData>(i.Id, i))); /////Вот тут все работает и загружается
LE>        }

LE>        public TagData GetTag(Int64 id)
LE>        {
LE>            return DataDictById[id]; ////Вот тут падает
LE>        }
LE>    }
LE>

LE>И собственно возникает такая проблема:
LE>1) Сначала было так:
LE>Несмотря на то, что данные загружаются в DataDictById нормально, при вызове GetTag в DataDictById этих данные не оказывается. То есть DataDictById остается вообще пустой.
LE>Хотя и инициализируется и дергается DataDictById в одном и том же запросе, то есть на теоретические проблемы многопоточности из-за использования статического класса тут скинуть не удастся.
LE>2) Потом неожиданно, после прохождения всего пути отладчиком, все теги закинулись и все стало работать хорошо и правильно. Я пробовал перезагружать студию, перекомпилировать — все работало. Я добавил новые теги. Из бд они опять же брались, отладчик показывал, что взялись все теги, но в GetTag на этот раз оказались только старые теги и оно опять падает.
LE>Собственно вопрос: что за чудеса не виражах такие?

В результате нашел косяк:
Конструктор статического класса вызывался далеко на всегда. То есть вообще как-то странно получается: скомпилировал проект, он запустился, статический класс используется, а статический конструктор не вызывается! Пора еще раз перечитать теорию.
Всем спасибо за помощь!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.