Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Разработка под ASP.NET > проблема с многопоточностью.


Автор: dmitryk1 16.9.2015, 12:35
В системе использую объекты в которых лежат запросы к базе.
запросов разных много и хранятся они в файлах.
Некоторые редкие, некоторые выполняются очень часто.

Для ускорения работы создал пул этих объектов и получаю их методом :
Код

        private static Dictionary<string, report> reps = new Dictionary<string, report>();

        public static report Getreport(string Id, string Path = "")
        {
            report rp;
                if (reps.ContainsKey(Id))
                {
                    rp = reps[Id];
                }
                else
                {
                    rp = new report(Id, Path);
                    reps.Add(Id, rp);
                }
            return rp;
        }


соответственно первое обращение объект кэширует, остальные используют уже имеющиеся объекты.

Основная проблема возникает с частоиспользуемыми объектами, когда одновременно в разных потоках начинают запрашиваться одинаковые объекты и тут:
Код

 reps.Add(Id, rp); 

выдаётся ошибка что ключ уже добавлен.
соответственно здесь: 
Код

  if (reps.ContainsKey(Id))

его ещё не было.
С этим можно как-то бороться? 

Автор: jsharp36 16.9.2015, 13:47
даже не вникал особо в код, очевидная бага. Для многопоточного кода используйте или блокировки или специальные структуры данных.

Dictionary не ходится для работы с разных потоков.
Наиболее простой способ починить, это обернуть в lock.

Код

        private static Dictionary<string, report> reps = new Dictionary<string, report>();

        private object syncObject = new object();
        public static report Getreport(string Id, string Path = "")
        {
            lock(syncObject)
            {
                report rp;
                if (reps.ContainsKey(Id))
                {
                    rp = reps[Id];
                }
                else
                {
                    rp = new report(Id, Path);
                    reps.Add(Id, rp);
                }
                return rp;
            }
        }


Это самый надежный способ. Но будет вызывать  блокировки. Можно иначе. (Я не проверяю код, если что, баги будут очевидные:

Код

        private static ConcurrentDictionary<string, report> reps = new ConcurrentDictionary<string, report>();

        public static report Getreport(string Id, string Path = "")
        {
                report rp;
                if (!reps.TryGetValue(Id, out rp))
                {
                    rp = new report(Id, Path);
                    resp[rp] = rp;
                }

                return rp;
        }


Тут возможно будет несколько раз создаваться репорт, пока не попадет в словарь. Если не использовать Add, а resp[rp] = rp;, то гарантированно падать не будет, если есть уже значение, то перезапишет. Вопрос только в оптимальности и можно ли два раза делать один и тот же репорт. Иногда блокировки будут хуже для производительности, иногда если часто пересекаются вызовы на одинаковые репорты, то репорты (особенно если там тяжелая в конструкторе логика).

Если надо надежно только одному потоку, то блокируйте lock-ом

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/problema-s-mnogopotochnostyu-id55f93808ae201526618b4567#findElement_E7045_55f948d1ae201537737f5d6a_0

Автор: dmitryk1 17.9.2015, 14:53
Спасибо, тоже уже в сторону лока смотрел. Но решил поконсультироваться. В принципе блокировки не особо страшны, поскольку отчёты добавляются в кэш только после чистки его или рестарта сервера, так что вариант более чем достаточный.

Хотел уточнить - когда я подобный код с блокировками отлаживал, при дебаге он внутрь лока заходил несколькими потоками. Или при дебаге он на локи не смотрит?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)