C# multi-threading problem part two

think2

Senior member
Dec 29, 2009
223
2
81
As described in my other recent post, I'm trying to write a C# program that sends a message on a serial port and waits for a reply which should come within 10 or 15 milliseconds, after which another message could be sent. If no reply comes, an ack timeout timer event initiates the sending of another message. There is also a timer event that puts messages in the queue of messages to be sent (function add_message_to_queue below). Hence there are three different threads (possibly on different CPU cores) that can call SendNextMessage.

Is the (semi-pseudo) code below correct from a "memory visibility" point of view? e.g. suppose the reply_received function runs and clears the wait_ack flag because there's no new message to send, when the ack_timeout function runs (on a different thread), will it see the true value of the wait_ack flag or could it see a stale value that just happens to already be in the level one cache associated with that thread. If so, do I have to use InterlockedExchange or similar to get around this? If the code is correct as it stands, is it because the C# lock function involves a memory barrier - and if so, how does that solve the "stale cache" problem?

Secondly, how can I solve the following problem. Suppose that a late reply arrives at the same time as the ack_timeout occurs and the late reply acquires sendMessageLocker just before the ack timeout. This means that the ack timeout thread is sitting waiting for the sendMessageLocker lock and as soon as it gets it, it will generate a spurious ack timeout and possibly send another message before the reply to the first message has had a chance to arrive. I can probably force the ack timeout time to be longer than any possible late arrival but I would like the code to handle the situation if possible.

Is there any easier way to do this that avoids the tricky problems above? e.g. I could make SendNextMessage a thread of its own that gets woken up by the other three threads which no longer need to lock sendMessageLocker.


Code:
        private void reply_received()
        {
            lock (sendMessageLocker)
            {
                reply_received_flag = true;
                SendNextMessage();
            }
        }

        private void ack_timeout()
        {
            lock (sendMessageLocker)
            {
                if (wait_ack)
                {
                    ack_timeout_occurred = true;
                    SendNextMessage();
                    increment_ack_timeout_counter();
                }
            }
        }

        private void add_message_to_queue()
        {
            // ... add to queue
            lock (sendMessageLocker)
            {
                SendNextMessage();
            }
        }

        private void SendNextMessage()
        {
            // the caller must lock
            //lock (sendMessageLocker)
            //{
                if (reply_received_flag || ack_timeout_occurred || !wait_ack)
                {
                    reply_received_flag = false;
                    restart_ack_timeout_timer();
                    ack_timeout_occurred = false;
                    wait_ack = false;
                    if (anything_to_send)
                    {
                        restart_ack_timeout_timer();
                        send_message();
                        wait_ack = true;
                    }
                }
                else if (wait_ack)
                {
                    // somehow check that the ack timeout timer event didn't get lost
                }
            //}
        }
 

Ajay

Lifer
Jan 8, 2001
15,454
7,862
136
I don't see where you'd have a stale cache situation here. Basically you are writing your own protocol and you have implemented a state machine. You need to write out that state machine an make sure there are no flaws (stuck state, ambiguous state, etc.). So do some reading on state machines, you can start here https://en.wikipedia.org/wiki/Finite-state_machine for a quick overview.
 

think2

Senior member
Dec 29, 2009
223
2
81
I don't need a state machine. There are only two states - idle and waiting for an ack.
 

Ajay

Lifer
Jan 8, 2001
15,454
7,862
136
You already have one. Anyway, trust your gut. I was willing to give you some hints, but I'm not going to do your homework for you.
 

gregulator

Senior member
Apr 23, 2000
631
4
81
Not necessarily related to your question but are you using a real serial port or a USB to serial? If USB to serial, there are a lot of gotchas.. more so than just regular serial. You will have issues with how USB is implemented (how often it is serviced) as well as buffering in those devices. Sometimes they will buffer and bunch of serial data and then dump it.
 

think2

Senior member
Dec 29, 2009
223
2
81
ok, thanks, it is USB to serial (RS485). I still get an ack timeout if I open a Java application (MPLAB) while my program is running. With an ack timeout of 200 milliseconds and the poll reply coming back within 20 milliseconds, I would have thought the poll reply would always beat the ack timeout but it doesn't seem to so maybe it's the USB problem you mention.
 

Ajay

Lifer
Jan 8, 2001
15,454
7,862
136
Not necessarily related to your question but are you using a real serial port or a USB to serial? If USB to serial, there are a lot of gotchas.. more so than just regular serial. You will have issues with how USB is implemented (how often it is serviced) as well as buffering in those devices. Sometimes they will buffer and bunch of serial data and then dump it.

Nice job, wouldn't have guess USB to serial. Seem like one needs to see if the device driver's IOCTL exposes flushes in it's API.
 

mike8675309

Senior member
Jul 17, 2013
507
116
116
Not sure why the particular implementation is being used for this. C# can support events on serial ports which removes the need for a timer at all other than to flag if no ack ever comes in some tolerance time period. If you could leverage things like the SerialDataReceivedEventHandler then you can stop worrying about the right time to read your message. Here is a starting point:
http://stackoverflow.com/questions/...way-to-read-a-serial-port-using-net-framework

More info on raising events in a thread safe way in .net
http://stackoverflow.com/questions/22356/cleanest-way-to-invoke-cross-thread-events
http://geekswithblogs.net/BlackRabb...ls-safely-and-efficiently-raising-events.aspx
https://blogs.msdn.microsoft.com/ericlippert/2009/04/29/events-and-races/

Other points to keep in mind with serial.
There is generally a buffer created for IO ports. (you aren't talking directly to the port)
C# is not precise for time., you may say every 15ms but it won't be that exact.
If using USB to Serial there can be other buffers at play that can slow data transmit or receive.