I am completely perplexed with how my Queue is function. I am attempting (and failing) to write a small multi-threaded application to collect and display data in C#.
After reading through Albahari's book and using the Consumer/Producer pattern he describes I got most of it to work except my data seems to get scrambled in the queue. Before getting queued the fields in my object have the following values
timeStamp = 6
data[] ={4936, 9845, 24125, 44861}
After being dequeued the data looks like
timeStamp = 6
data[] = {64791, 19466, 47772, 65405}
I don't understand why the values in the data filed are being changed after the dequeue? I am perplexed so I thought I'd throw it out there to see if anyone can point me in the right direction to fixing this or to point me in a different direction to proceed.
Relevant Code
Custom Object for data storage
Relevant objects and Fields. The class sensorData is a seperate class use to store my calculations.
public class sensorData
{
public const int bufSize = 4;
public UInt16[] data = new UInt16[4];
public double TimeStamp = 0;
public int timeIndex = 0;
}
The following fields are used to setup the queue and signals between the enqueue and dequeue threads.
EventWaitHandle wh = new AutoResetEvent(false);
Queue<sensorData> dataQ = new Queue<sensorData>();
object locker = new object();
Enqueue Method/Thread
This is my worker thread it calculates four Sine curves and queues the result for processing. I also write the results to a file so I know what it has calculated.
private void calculateAndEnqueueData(BackgroundWorker worker, DoWorkEventArgs e)
{
int j = 0;
double time = 0;
double dist;
UInt16[] intDist = new UInt16[sensorData.bufSize];
UInt16 uint16Dist;
// Frequencies of the four Sine curves
double[] myFrequency = { 1, 2, 5, 10 };
// Creates the output file.
StreamWriter sw2 = File.CreateText("c:\\tmp\\QueuedDataTest.txt");
// Main loop to calculate my Sine curves
while (!worker.CancellationPending)
{
// Calculate four Sine curves
for (int i = 0; i < collectedData.numberOfChannels; i++)
{
dist = Math.Abs(Math.Sin(2.0 * Math.PI * myFrequency[i] * time);
uint16Dist = (UInt16)dist;
intDist[i] = uint16Dist;
}
// Bundle the results and Enqueue them
sensorData dat = new sensorData();
dat.data = intDist;
dat.TimeStamp = time;
dat.timeIndex = j;
lock (locker) dataQ.Enqueue(dat);
wh.Set
// Output results to file.
sw2.Write(j.ToString() + ", ");
foreach (UInt16 dd in dat.intData)
{
sw2.Write(dd.ToString() + ", ");
}
sw2.WriteLine();
// Increments time and index.
j++;
time += 1 / collectedData.signalFrequency;
Thread.Sleep(2);
}
// Clean up
sw2.Close();
lock (locker) dataQ.Enqueue(null);
wh.Set();
sw2.Close();
}
Example line in the output file QueuedDataTest.txt
6, 4936, 9845, 24125, 44861,
Dequeue Data Method
This Method dequeues elements from the queue and writes them to a file. Until a null element is found on the queue at which point the job is done.
private void dequeueDataMethod()
{
StreamWriter sw = File.CreateText("C:\\tmp\\DequeueDataTest.txt");
while (true)
{
sensorData data = null;
// Dequeue available element if any are there.
lock (locker)
if (dataQ.Count > 0)
{
data = dataQ.Dequeue();
if (data == null)
{
sw.Close();
return;
}
}
// Check to see if an element was dequeued. If it was write it to file.
if (data != null)
{
sw.Write(data.timeIndex.ToString() + ", ");
foreach (UInt16 dd in data.data)
sw.Write(dd.ToString() + ", ");
sw.WriteLine();
}
else
{
wh.WaitOne();
}
}
Output result after dequeueing the data and writing it to DequeueDataTest.txt
6, 64791, 19466, 47772, 65405,
Updates 1:
Location of Locks in current code.
I have edited the code to place locks around the writing data to the file. So the code blocks I have locks around are as follows.
In the CalculateAndEnqueueData() method I have
lock (locker) dataQ.Enqueue(dat);
wh.Set
lock(locker)
{
sw2.Write(j.ToString() + ", ");
foreach (UInt16 dd in dat.intData)
{
sw2.Write(dd.ToString() + ", ");
}
sw2.WriteLine();
}
In the dequeueDataMethod() I have two areas with locks the first is here
lock(locker)
if (dataQ.Count > 0)
{
data = dataQ.Dequeue();
if (data == null)
{
sw.Close();
return;
}
}
which I assume locks locker for the code in the if block. The second is where I write the to the file here
lock (locker)
{
sw.Write(data.timeIndex.ToString() + ", ");
foreach (UInt16 dd in data.intData)
sw.Write(dd.ToString() + ", ");
sw.WriteLine();
if (icnt > 10)
{
sw.Close();
return;
}
this.label1.Text = dataQ.Count.ToString();
}
That's all of them.
The problem is due to no synchronisation on the StreamWriter you are writing to. The order is not sequential.
Is it because you are writing to the same array UInt16[] intDist
over and over again? Shouldn't you be using separate arrays for each sensorData
object? (Btw, is sensorData.intData
suppose to be sensorData.data
in your sample code?)
CLARIFICATION:
Only one intDist
array is created in calculateAndEnqueueData()
, so different sensorData
instances are all sharing the same array --- this is ok if the adding+writing and removal+writing occur in lock-step; otherwise, some data points may be missing/repeated.
SUGGESTION:
Populate sensorData
instances directly, without using the intDist
array, in calculateAndEnqueueData()
:
// create new sensorData instance
sensorData dat = new sensorData();
dat.TimeStamp = time;
dat.timeIndex = j;
// Calculate four Sine curves
for (int i = 0; i < collectedData.numberOfChannels; i++)
{
dat.data[i] = (UInt16) Math.Abs(Math.Sin(2.0 * Math.PI * myFrequency[i] * time);
}
// enqueue
lock (locker) dataQ.Enqueue(dat);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.