Есть ли безопасный способ для быстрого обмена данными между потоками? - PullRequest
1 голос
/ 17 июня 2019

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

Я программирую на c #, и я решил использовать отдельный поток для получения данных с тензодатчика.У меня такой вопрос: как я могу использовать данные, полученные в потоке, потокобезопасным способом?Например, чтобы показать их на диаграмме.

Это поток, который я вызываю для получения данных в очереди.

Thread t = new Thread(() =>
            {
                Thread.CurrentThread.IsBackground = true;
                while (save_in_queue)
                {
                    Thread.Sleep(1);
                    if (queue.Count <= 1000)
                    {
                        queue.Enqueue(Frm_main.ComPh1.LeggiAnalogica(this.Address));
                    }
                    else
                    {
                        queue.Dequeue();
                        queue.Enqueue(Frm_main.ComPh1.LeggiAnalogica(this.Address));
                    }
                }
            });
            t.Name = "Queue " + this.name;
            t.Start();

Этот метод используется для привязки очереди, заполненной потоком, и очередью.в основном

        public void SetData(Queue<int> q)
        {
            this.data = q;
        }

Это таймер, который я использую в главном приложении для установки данных для серии

private void timer1_Tick(object sender, EventArgs e)
        {
            List<int> dati = new List<int>();
            lock (data)
            {
                dati = data.ToList();
            }
            grafico.Series[serie.Name].Points.Clear();
            for (int x = 0; x < dati.Count; x++)
            {
                DataPoint pt = new DataPoint();
                pt.XValue = x;
                pt.YValues = new double[] { dati.ElementAt(x) };
                grafico.Series[serie.Name].Points.Add(pt);

            }
        }

Этот код не работает, потому что иногда я получаю исключение "Коллекциябыл изменен; операция перечисления может не выполняться "в строке dati = data.ToList ();

Скажите, что довольно ясно, почему я получаю это исключение.Но как решить эту проблему?

Я бы хотел избежать использования слишком большого количества «блокировок» или слишком большого количества переменных синхронизации, чтобы не снизить производительность сбора данных, что на данный момент отлично.

Ответы [ 2 ]

0 голосов
/ 17 июня 2019

Не делайте этого в своей ветке:

lock (data) {
    dati = data.ToList();
}

Вы используете очередь для двух разных целей; Вы используете его для передачи данных между двумя потоками, что хорошо; но вы также используете его в качестве буфера истории для предыдущих образцов данных. Это плохо.

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

Это тоже плохо:

if (queue.Count <= 1000) {
    queue.Enqueue(Frm_main.ComPh1.LeggiAnalogica(this.Address));
}
else {
    queue.Dequeue();  <== THIS IS BAD!
    queue.Enqueue(Frm_main.ComPh1.LeggiAnalogica(this.Address));
}

Одна из проблем заключается в том, что вы заставляете производителя управлять буфером истории (например, ограничивая длину очереди), но о длине заботится потребитель.

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


Производитель должен сделать только одно: он должен прочитать данные с датчика и поместить данные в очередь.

Очередь должна использоваться только для одной цели: для передачи новых данных между потоками.

Производитель должен заблокировать очередь достаточно долго, чтобы получить новые данные из очереди и скопировать их в свою собственную частную коллекцию.


Многопоточное программирование часто может быть нелогичным. Одним из примеров является; Если вы можете уменьшить количество времени, которое потоки тратят на доступ к общему объекту, на , увеличив объем работы, которую должен выполнять каждый поток, это часто повышает общую производительность программы. Это связано с тем, что блокировка обходится дорого, а доступ к областям памяти, которые были затронуты другими потоками, дорог.

0 голосов
/ 17 июня 2019

Возможно, вы захотите проверить пространство имен Concurrent Collections, которое обеспечивает поточно-ориентированную реализацию некоторых коллекций

Пространство имен System.Collections.Concurrent предоставляет несколько Потокобезопасные классы коллекции, которые должны использоваться вместо соответствующие типы в System.Collections и System.Collections.Generic пространства имен, когда несколько потоков одновременный доступ к коллекции.

https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent

Таким образом, вы можете использовать System.Collections.Concurrent.ConcurrentQueue вместо System.Collections.Queue , чтобы предоставить бесплатное решение для блокировки вашей проблемы.

...