简体   繁体   中英

Why in C# is Queue scrambling the data in its elements?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM