Queues only for string messages?

Hi there,

Using queues seemed to be a trivial task. Using strings seems to work OK, however when I did some experimenting using other datastructures then strings I noticed some strange phenomena. Messages were mixed up, so no FIFO behaviour.
In case you built in a sleep(100) on the producer side, it seems to work OK!?
Possible explanation: queues only copy strings into the queue, when using other datastructures only the pointer is copied and queued???

All examples on the internet are trivial and only use string messagesā€¦Perhaps somebody has a working example not using strings, that would be nice.

Weā€™re probably going to need a code sample illustrating the problem. It isnā€™t clear (to me at least) exactly what the observed and expected behaviors are.

Sorry I must have made a coding error. Using datastructures other than strings seems to work OK.
Case closed.

using System;
using Microsoft.SPOT;
using System.Collections;
using System.Threading;

namespace QueueTest
{
    public class Program
    {
        struct QueStruct
        {
            public int Count;
            public string Msg;
        }
        QueStruct Msg;
        public static Queue Que1 = new Queue();
        public static Thread Consumer = new Thread(ConsumerThread);
        public static void ConsumerThread()
        {
            while (1 == 1)
            {
                //ebug.Print("Que count: " + Que1.Count);
                while (Que1.Count > 0)
                {
                    QueStruct Reader = (QueStruct)Que1.Dequeue();
                    Debug.Print("Reader:  " + " : " + Reader.Count + "  "+ Reader.Msg);
                }
                Thread.Sleep(100);
            }
        }
        public static void Main()
        {
            int Count = 0;
            QueStruct Msg = new QueStruct();
            Consumer.Start();
            //producer start
            while (1 == 1)
            {
                Msg.Count = Count++;
                Msg.Msg = (Count + 1000).ToString();
                Que1.Enqueue(Msg);
                Msg.Msg = (Count + 2000).ToString();
                Que1.Enqueue(Msg);
                Thread.Sleep(1000);
            }
        }

    }
}



Output queue consumer:
Reader: : 0 1001
Reader: : 0 2001
Reader: : 1 1002
Reader: : 1 2002
Reader: : 2 1003
Reader: : 2 2003ā€¦

Well, I see a problem there. Because you only ā€˜newā€™ the variable ā€˜Msgā€™ one time, you are essentially inserting the same instance object into the queue over and over again. The same thing would have happened with strings. The output looks right here only because of pure good luck of timing - and perhaps you didnā€™t have the same good luck with strings.

If you are trying to create a proper producer/consumer queue, you need to ā€˜newā€™ up a new instance of your queue-element object for each call to enqueue (or move to some sort of circular buffer on top of an array, which do less thrashing of the heap).

EDIT: Move this inside the producer loop, and it will function as I think you are intendingā€¦ You can prove that the current one is broken by making the producer Sleep much smaller than the readerā€™s. Then make this change and you will see it work again, regardless of timing.

Well, you got me this time. Itā€™s a struct and I missed that.

Structs get copied by value. Strings (and other ref types) get copied by reference.

Thatā€™s why this works and strings didnā€™t. My explanation was correct, but I missed the fact that you are using structs, which is why it is working.

Apologies for missing that. If you change to a ref type (like string) make sure you change the logic as I described.

I dont think so.
I tested both options (structs and strings), changed the timing as you suggested, the data is still output correctly, so I conclude that all objects are copied into the queue (luckiliyā€¦).

Whatever floats yer boat (er, code), but Iā€™m confident that the laws of ref/value typing have not been repealed any time recently.

Of course, if you are doing string operations (such as the concatenations shown in your struct code example) then you are actually newā€™ing up new string instances without actually doing an explicit ā€˜newā€™ which might be why it looks like it is working without ā€˜newā€™, even though strings are ref types. Strings are ref types, but they are immutable ref types and any modifications result in a new string.

Change your struct to a class, fiddle with the timing, and I expect things will not go so smoothly.

Right, Using a class was the reason my queue application did not work as expected in the first placeā€¦( I did not publish that code).
The fact that .NETMF makes a copy of every object seems obvious , as MS states that an Object is queued (I read ā€œcopiedā€ into the queue).

My question would be, is my code incorrect, then what would you suggest?

Your code works with the struct, so your code is correct as-is (for single threaded producers). To be clear, with the structs, thereā€™s nothing wrong with the code, and it is efficient in that it does not churn the heap (the Msg structure is copied bit-wise into a queue element, so there are no heap allocations except as needed for the expansion of the queue).

The way you describe it isnā€™t actually accurate, though and might lead you to making mistakes when dealing with ref objects (classes). The queue does not make a copy ā€˜of every objectā€™. What it does is copy value objects verbatim into the queue, and copy pointers to ref objects. Presuming you want unique items in your queue, then enqueue->new->mutate->enqueue works with ref objects and enqueue->mutate->enqueue works with value types (int, float, struct, etc).

Strings are kind of a special case because they are immutable ref objects and they get reallocated whenever mutated (changed) in any way. Any change to a string results in a new string and the old one is GCā€™d if thereā€™s no other ref to it.

EDIT : Just to be crystal clear, when I said your code was ā€˜wrongā€™, I misread it thinking you were inserting ref objects, not structs. My bad.

Thanks for the information, it inspires me to do some further experiments. My code is not threadsafe so I need a lock() in the producer.
Is the information about the inner workings of the queue documented somewhere?

Use the source, Luke. :slight_smile:

[url]Reference Source

1 Like

Thanks :dance:

Except that the queue internally stores the items in an object array requiring boxing (heap allocation) of a target struct before it can be bitwise copied.

1 Like

Did not know that - thanks for the clarification.