Почему я получаю ошибки "Коллекция была изменена" из ConcurrentDictionary, когда я только читаю значения? - PullRequest
0 голосов
/ 05 июля 2018

У меня есть многопоточный консольный проект, который привязан к RFID-антеннам, которые при включении постоянно сканируют. У меня перед ними около 500 RFID-меток, и они сканируют от 15000 до 20000 за 30 секунд.

Сначала я собирался использовать ConcurrentDictionary или Lazy-версию ConcurrentDictionary, но он не добавлял (и, возможно, не обновлял) теги. Я проверил это, просто создав базовый класс строки List и сохранив тег в том же событии, в котором сканируется тег, и в отдельном событии таймера. Я сравнил отдельный список основного списка с обоими ConcurrentDictionaries.

Чтобы объяснить использование ConcurrentDictionary, я сохраняю объект struct, который содержит свойство datetime (время сканирования) и свойство expired, чтобы в моем событии timer я мог очистить коллекцию элементов с истекшим сроком. Объект struct принимает объект timepan, отмечающий, как долго он хорош до истечения срока его действия. Я использую обе версии ConcurrentDictionary, потому что я все еще проверяю, какая из них лучше.

Затем я подумал сохранить общий список строк и использовать его в качестве сборщика необработанных данных, и каждые 30 секунд или около того, получать отдельные значения и проходить через них и использовать addorupdate для обновления ConcurrentDictionary. Проблема в том, что я получаю исключения InvalidOperation (коллекция была изменена), повторяющий список общего списка, хотя я только читаю значения. Цикл ниже. Я также попытался сделать копию общего списка и использовать ToList () в цикле, и я все еще получаю исключение.

static LazyConDeDupe<string, ConEntry> lDedupe = new LazyConDeDupe<string, ConEntry>(new TimeSpan(0, 0, 30));
static private ConDeDupe dc = new ConDeDupe(new TimeSpan(0, 0, 30));
static private List<string> epcs = new List<string>();
private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        List<string> missingList;
        List<string> lazyMissingList;

        lock (epcs)
        {
            List<string> disScans = epcs.Distinct().ToList();

            for (int i = 0; i <= disScans.Count - 1; i++)
            {
                dc.Add(disScans[i], System.DateTime.UtcNow);
                lDedupe.AddorUpdate(disScans[i], (k) => new ConEntry(System.DateTime.UtcNow, new TimeSpan(0, 0, 30)),
                                                 (r, v) => new ConEntry(DateTime.UtcNow, new TimeSpan(0, 0, 30)));
            }

            missingList = dc.FindNonExistantKeys(epcs);
            lazyMissingList = lDedupe.FindNonExistantKeys(epcs);


            if (missingList.Count > 0)
            {
                System.Diagnostics.Debug.WriteLine("Found EPCs Missing");
                System.Diagnostics.Debug.WriteLine(string.Format(@"Raw Count: {0}, Missing: {1}", epcs.Count.ToString(), missingList.Count.ToString()));
                missingList.ForEach(x => System.Diagnostics.Debug.WriteLine(String.Format("Missing EPC: {0}", x)));
                System.Diagnostics.Debug.WriteLine(string.Format(@"Raw Count: {0}, Lazy Missing: {1}", epcs.Count.ToString(), lazyMissingList.Count.ToString()));
                System.Diagnostics.Debug.WriteLine(string.Format(@"Distinct Raw Count: {0}", disScans.ToList().Distinct().Count().ToString()));
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("None Missing - EPCs in List");
                System.Diagnostics.Debug.WriteLine(string.Format(@"Raw Count: {0}, Missing: {1}", epcs.Count.ToString(), missingList.Count.ToString()));
                System.Diagnostics.Debug.WriteLine(string.Format(@"Raw Count: {0}, Lazy Missing: {1}", epcs.Count.ToString(), lazyMissingList.Count.ToString()));
                System.Diagnostics.Debug.WriteLine(string.Format(@"Distinct Raw Count: {0}", disScans.ToList().Distinct().Count().ToString()));
            }

            epcs.Clear();
        }
        if (dc.Count > 100000)
        {
            dc.CleanUp();                

        }
        else if(lazyMissingList.Count > 0) lDedupe.CleanUp();

    }

private static void OnTagsReported(ImpinjReader sender, TagReport report)
    {
        //MessageBox.Show(string.Format("RFID Scanned, Tags:{0}", report.Tags.Count.ToString()));
        foreach (Tag tag in report)
        {
            Console.WriteLine(string.Format(@"EPC: {0}", tag.Epc.ToString()));
            try
            {
                //dc.Add(tag.Epc.ToString(), System.DateTime.UtcNow);
                //lDedupe.AddorUpdate(tag.Epc.ToString(), (k) => new ConEntry(System.DateTime.UtcNow, new TimeSpan(0, 0, 30)),
                //                                        (r, v) => new ConEntry(DateTime.UtcNow, new TimeSpan(0, 0, 30)));
                epcs.Add(tag.Epc.ToString());
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
        }
    }

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

EDIT: Таким образом, я перестроил свой код в прошедшем событии, но чтобы упростить задачу, проблема возникает в этом фрагменте кода.

List<string> disScans = null;
lock (epcs)
{
    disScans = epcs.Distinct().ToList(); <------- Error occurs here
    epcs.Clear();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...