Solved

Please explain why serial I/O read is a "blocking event" so can't be in button handler

Posted on 2011-09-10
17
283 Views
Last Modified: 2012-05-12
Please explain why in a button event handler I can not send a message to a serial port and then read the reply.
private void button1_Click(object sender, EventArgs e)
{
    serialPort1.Write(msg);
    serialPort1.Read(reply);  //can't do this
}

Open in new window

I've already implemented the read in a serialPort1_DataReceived event handler. It's my boss who knows only old procedural programming languages who doesn't understand why I can't simply translate his old procedural program to C# with a GUI. I try and explain "event-driven" programming to him. he still wants to know why it can't be done. I know it can't be done, and it has something to do with "blocking events". I'd like to learn more of the details behind why this can't be done.
0
Comment
Question by:deleyd
  • 7
  • 6
  • 3
  • +1
17 Comments
 
LVL 1

Expert Comment

by:ananken
ID: 36517263
The reason is that when you call the Read() method, you rely on the fact that the other side called Write(). Until the other side has actually called the Write() method, your code will block, since it's waiting for that to take place.

/ananken
0
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 250 total points
ID: 36517267
A WinForms application has an internal message pump that is basically a never ending loop that routes messages generated by the operating system and from user interaction.  If you place a blocking call in the same thread as the main UI, which is the case when placed in a button handler, then the message pump also gets blocked.  This results in an unresponsive application that will not respond to user interaction and if left long enough will experience a "white out" (doesn't repaint).  Therefore, in a WinForms application, blocking calls should be placed into another thread.
0
 
LVL 82

Assisted Solution

by:Dave Baldwin
Dave Baldwin earned 250 total points
ID: 36517274
The last time I wrote for PC serial ports (DOS 6.22!), you wrote your own interrupt service routine for both send and receive and they were usually character based, not 'message' or 'string' based.  I don't think you can even do that past Windows 98 or so, the serial port drivers are in a protected part of the operating system.

In the code you have above, the "serialPort1.Write(msg);" passes a string to a driver and lets the driver and interrupt routines send the data.  If the second part did work, it would block your entire program until it finished.  Even in the 'old' way, we would not have done that.  We would let the driver or ISR receive the bytes and poll the routine to collect them into a buffer where we could see when it had the reply.

If your boss really did that kind of programming and believes that should work, I think he is remembering wrong.
0
 

Author Comment

by:deleyd
ID: 36517372
He coded it in MATLAB. The code essentially goes like this:
s = serial(port,'Name',...
fopen(s)
fwrite(s,pingmsg)
<wait for reply (bytes to read)>
out = fread(s,s.BytesAvailable);

Open in new window

You simply open the port, send a ping, wait for a reply.

I tried that in a button event handler. The ping was sent, but program hung on my following 'read reply' statement.

I think it has to do with the message pump that Idle_Mind mentioned. I used to work with those a lot.

So if I understand correctly:

1. Thread 1 has a message pump

2. Thread1 writes to the serial port

3. Bytes get sent out the serial port

4. Thread1 issues a 'read'

5. Thread1 receives a message that there are bytes to read

6. Thread1 never processes the 'there are bytes to read' message because it is waiting for it's 'read' request to complete.

OK I still don't quite see why it doesn't work. Why does the OS send a "there are bytes to read" message to the message pump instead of just completing the 'read' request?

I've always wanted to know what goes on "under the hood" of C# and .NET Framework, but all the books I've found hide that stuff, saying "it just works."

I think there's a "message sniffing" tool out there somewhere. Possibly Sysinternals? I'd learn a lot if I could see the actual messages going through the pump like I used to do.
0
 
LVL 82

Expert Comment

by:Dave Baldwin
ID: 36517458
I would bet that the Matlab code isn't doing exactly what you think it is.  I would bet that it interprets that code and actually has well written routines that run it in the background.  It is what I would do.

Even in assembly language long ago, you never make your main program wait on the serial port.  Serial ports are very slow in comparison to the CPU.  Putting both the write and the read in that same routine will stall your program if everything works perfectly and crash it if it doesn't respond quickly.

I'd write more but I have to go.  I'll check back tomorrow.
0
 

Author Comment

by:deleyd
ID: 36517621
In the MATLAB implementation my boss did a simple

1. Send Message

2. Read Reply

Which was simple with MATLAB. Not so easy with event-driven code since there we're just sending messages, messages, messages,...

and reading replies, replies, replies,...

It's no longer easy to know what reply goes with which message.

Sometimes the device just replies with a NAK. Well thank you, but what message didn't you get?

My boss thought it would be a simple quick process to translate the MATLAB code to C# and that I would be able to do it in a day or two. I have to explain why it's not that simple. He's familiar with FORTRAN, C and MATLAB. I have to explain it's not like translating from French to Spanish, it's more like translating from a book to a movie.

(Yes when I tested his MATLAB code it did hang the entire computer once requiring a hard power down reboot.)
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36517665
Right...so just create another thread to place your I/O operations on.  Then you can send --> wait --> {know what the response was} for each transmission.
0
 
LVL 82

Expert Comment

by:Dave Baldwin
ID: 36518289
Your boss is not familiar enough with writing serial code in C or Matlab.  The language does not change the process.  And it doesn't matter if it worked every other time.  The method of "sending the message" and "waiting on the reply" is guaranteed to fail eventually.  I'm not a C# programmer but the methods haven't changed since 1980 when I wrote my first routine in assembly language.  If you make your program wait on the reply, then something will happen to make it fail.

In a quick search, I don't see anything that looks usable in C# but maybe that's because I don't know C#.  I do kind of like @Idle_Mind's idea of putting it in another thread.  But you still have to be able to check status for things like the serial cable unplugged.
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 

Author Comment

by:deleyd
ID: 36543916
I tried starting a new thread which quickly threw several messages at the port. Discovered the device can't handle having several messages coming at it that fast. It only replied to the first message.

I then added a delay between messages to give the device time to reply. That worked.

However I really need to sync the sending & receiving, so I can match up sent messages with received replies, and know when it's OK to send the next message, which sounds complicated:
create new thread
send a message
wait for a notification that a reply has been received
also set a timeout timer in case there is no reply which sends a notification that the timer has expired
if reply received cancel timeout timer
prevent other messages from being sent via other events while this is happening
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36544205
The send/receive should be on its own thread.
The timeout can be achieved using a standard timer on the form.
You can use two ManualResetEvents to toggle the status of a receive event and a timeout event.
http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx

How to combine them?..yet another thread that listens to the status of both the receive ManualResetEvent and the timeout ManualResetEvent.

This can be done with WaitHandle.WaitAny().
http://msdn.microsoft.com/en-us/library/tdykks7z.aspx
0
 
LVL 82

Expert Comment

by:Dave Baldwin
ID: 36544637
@deleyd, that's almost exactly what has always been done for serial port messaging (post #36543916).  I think you understand the process now.
0
 
LVL 82

Expert Comment

by:Dave Baldwin
ID: 36544654
The one thing you might add is a check of the port status before you send a message.  That way if the cable is disconnected or the device has signalled that it's not available, you don't have to wait for the timeout to know that.
0
 

Author Comment

by:deleyd
ID: 36546156
Into the deep fun I go.

(This question should have 10,000 points! Thanks for all the help everyone.)

How do I check the port status? I'm looking at SerialPort Class and not seeing any "I'm unplugged" property.

(I've wondered what would happen if I unplugged the USB cable and then plugged it back in. Was going to test that eventually to see what happened.)
0
 
LVL 82

Expert Comment

by:Dave Baldwin
ID: 36546446
USB cable?  Does that mean you don't have a 'real' serial port?  You have a USB to Serial Port adapter?  That could be interesting because USB is a networking protocol that doesn't provide status from the other end unless it is implemented in the driver.

Is this what you're using?  http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx  If so, I don't see any status info there either.  Maybe USB adapter isn't that different then.
0
 

Author Comment

by:deleyd
ID: 36546455
Oh I also sometimes get the serialPort1_DataReceived event fired but the serialPort1.BytesToRead = 0.

Don't know why that happens once in awhile.

(I think it happens after a rather long message is received, and I read the whole thing the first time the receive event gets fired, but then I get another firing of the event, except there's 0 bytes to read.)
0
 

Author Closing Comment

by:deleyd
ID: 36578257
I discovered I can do on a background worker thread the port write followed by a port read.

(Main UI thread with message pump must have something to do behind the scenes with the read, because I can't do this on the main UI thread, even if I don't mind the program hanging until the read completes.)
0
 
LVL 82

Expert Comment

by:Dave Baldwin
ID: 36578504
Thanks for the points, glad you got it working.
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

More often than not, we developers are confronted with a need: a need to make some kind of magic happen via code. Whether it is for a client, for the boss, or for our own personal projects, the need must be satisfied. Most of the time, the Framework…
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now