0

I have found that the C# serial port implementation can be pretty flaky when it comes to the datarecieved event, so I decided to implement my own asynchronous read using the underlying stream. I implemented a continuous read loop that I would expect to get a stack overflow due to recursion, but for some reason it doesn't. Here is the code:

SerialPort port = new SerialPort("COM0");
Stream bar = port.BaseStream;
Action foo = null;
foo = () =>
{
     byte[] buf = new byte[256];
     AsyncCallback callback = ar =>
     {
         int bytesRead = bar.EndRead(ar);
         //call event sending recieved bytes to main program
         foo();
     };
     bar.BeginRead(buf, 0, 8, callback, null);
};
foo();

I suspect the recursive calls to foo to keep making new stack frames until the system would crash, but apparently that is not the case.

So my question is, why do the recursive calls to foo not cause a stack overflow?

Elias
  • 1,367
  • 11
  • 25
  • It takes quite a few callbacks to fill the 1MB default stack. Are you sure you let it run long enough? I guess the recursion rate is limited by the serial port speed. When you place a breakpoint in foo() and examine the call stack after a few callbacks, what do you see? – Eric J. May 06 '15 at 17:18
  • The call stack doesn't seem to be very useful, all it displays is "Program.Main.AnonymousMethod_0()". I am reading sensor data at 2 Kb/s so It may be possible that the stack just takes a while to fill up. – Elias May 06 '15 at 17:27
  • If you're using SerialPort, use SerialPort.BaseStream and nothing else. Full stop. There are numerous articles you can easily find as to why this is true. – Jeff Dec 09 '15 at 06:01

1 Answers1

2

There is no recursion in this code. The C# compiler rewrites the lambda expression into a class with an unspeakable name. The buf variable becomes a field of that class. You can see it back if you use ildasm.exe on the assembly, the class name will resemble <>c__DisplayClass.

The callback runs on a threadpool thread, started when the async read completes. The "recursive" call to foo() just calls BeginRead() again and the tp thread terminates.

There are no obvious reasons why this code would work better than the DataReceived event, the underlying plumbing is identical.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • From my digging in the c# source code, I found that there might be a slight difference between the `serialport.datarecieved` and the `serialstream.beginread`. It seems like the datarecieved approach uses polling and calls `getoverlappedresult` from kernel32 in a loop to trigger the datarecieved event. The beginread approach differs in that it calls `WaitForSingleObject` from kernel32 on a handle that is passed to the async read operation instead of polling. The difference is polling vs blocking until a signal. – Elias May 06 '15 at 20:16