有没有在线程之间快速交换数据的安全方法?

时间:2019-06-17 07:50:53

标签: c# multithreading collections thread-safety

我正在设置一个应用程序,该应用程序从称重传感器读取数据,并基于读取的数据实时中断电动机的推力。从称重传感器上读取高频信号至关重要。

我正在用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 :(得分:0)

您可能要检查并发集合名称空间,该名称空间提供某些集合的线程安全实现

  

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 ,以提供无锁解决方案你的问题。

答案 1 :(得分:0)

不要在您的使用者线程中执行此操作:

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));
}

一个问题是,您要让生产者管理历史记录缓冲区(例如,通过限制队列的长度),但消费者却在乎长度。

另一个问题是生产者没有锁定队列。如果 any 线程需要锁定数据结构,那么每个线程都需要锁定它。


生产者应该只做一件事:它应该从传感器读取数据,并将数据填充到队列中。

该队列仅用于一个目的:在线程之间通信新数据。

生产者应将队列锁定足够长的时间,以从队列中获取新数据,然后将其复制到其自己的私有集合中。


多线程编程通常可能违反直觉。一个例子是;如果您可以通过增加每个线程必须完成的工作量来减少线程用于访问共享对象的时间,那么通常可以提高程序的整体性能。那是因为锁定是昂贵的,并且因为访问其他线程接触过的内存位置是昂贵的。