相关文章推荐
刚失恋的黄瓜  ·  集成 Oracle Designer ...·  1 年前    · 
爱听歌的夕阳  ·  SCEP certificate ...·  1 年前    · 
开朗的麻辣香锅  ·  Azure DevOps Server ...·  1 年前    · 
礼貌的萝卜  ·  Laravel macro ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I'm experiencing a weird behavior while trying to stop a SerialPort: the DataReceived event continues to fire after unsubscribing and after calling close ! (see StopStreaming in the following code). As a result, in my event handler code I get an InvalidOperationException with the message that "The port is closed".

What am I missing? What is the correct way to close the port and stop the events?

EDIT: I get this error every time I run my code. So this is not a race condition that happens randomly but rather a systematic problem indicating a completely broken code! However, I fail to see how...

private SerialPort comPort = new SerialPort();
public override void StartStreaming()
  comPort.Open();
  comPort.DiscardInBuffer();
  comPort.DataReceived += comPort_DataReceived;
public override void StopStreaming()
  comPort.DataReceived -= comPort_DataReceived;
  comPort.Close();
  isStreaming = false;
private void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
  if (e.EventType == SerialData.Chars)
    SerialPort port = (SerialPort)sender;
    int N = comPort.BytesToRead;
    for (; N > 0; N--)
      byte b = Convert.ToByte(comPort.ReadByte());
      //... process b

EDIT: following the suggestions, I changed StopStreaming code to something like this:

public override void StopStreaming()
  comPort.DataReceived -= comPort_DataReceived;
  Thread.Sleep(1000);
  comPort.DiscardInBuffer();
  Thread.Sleep(1000);
  comPort.Close();
  isStreaming = false;

It seems to work now but I'm not really that happy. I wish there was a more effective way to remove the callback rather than inserting sleep periods in the program.

Interesting idea. I tried it but didn't help :( I added it in StopStreaming right after the part that removes the event. – AlefSin Dec 14, 2012 at 10:12

Your DataReceived event handler is called on a threadpool thread. And yes, they've got the awkward habit of running your code at an unpredictable time, it is not instant. So it is fairly inevitable that, if the device is actively sending data, that it can race with your Close() call and run after you closed it. Unsubscribing doesn't fix it, the threadpool thread already got its target method.

Do realize what you are doing to trigger this problem, you are closing the port while the device is sending data. That's not great, it is guaranteed to cause data loss. But not unlikely to happen when you are debugging your code since you don't actually care about the data.

A counter-measure is to turn off handshaking so the device cannot send anything anymore. Discard the input buffer. Then sleep for a while, a second or two, to ensure that any threadpool threads in-flight have completed running. Then close the port. A very pragmatic one is to simply not close the port, Windows will take care of it when your process terminates.

Thanks for your answer. I prefer to close the port somehow. This is in fact some kind of simple port monitor so it is true that the communication is going to be cut in the middle of transmission. The data loss, however, is not that important. I'd like to find a robust solution to connect/disconnect as many time I like during the lift time of the process. – AlefSin Dec 14, 2012 at 13:08 I tried it and seems to work for now. I'm not happy though as it looks like a temporary hack rather than a robust solution. I'm going to accept your answer though. Thanks. Since the is in fact a virtual comport provided by the FTDI chip, I'm going to try completely different approach next based on their D2XX API that does not need any SerialPort nonesense. – AlefSin Dec 14, 2012 at 13:32 Interestingly, it looks like the devs went to a lot of effort to prevent this exact situation, as seen from SerialPort.cs and SerialStream.cs. Unfortunately, it looks like the performance tradeoff was not worth it, as I can confirm I can still get this issue when concurrently closing the serial port as of 2020. – glopes Aug 26, 2020 at 10:20

Looks like multi-threading issue.

SerialPort.DataReceived is raised in a worker thread from thread pool. You're closing port from the thread, that differs from the thread, where SerialPort.DataReceived raised in.

You can handle InvalidOperationException or write some synchronization code to solve this problem.

Update.

The problem is that if your devices sends data intensively, SerialPort queues more and more work items to thread pool. Even if your code will sleep any time before Close, it can be not enough. Unfortunately, SerialPort has an ugly implementation. It hasn't an option, which could tell "please, stop spam me with your data".

Hence, the concrete solution depends on device's protocol and handshaking parameters.

Well, I assume the call back runs in a thread that otherwise I have no access to. I don't see a clear, bullet proof way to avoid race conditions while at the same time guarantee that the callback would not get blocked or miss data. Do you have a suggestion how to practically do that? – AlefSin Dec 14, 2012 at 13:13 Thanks for your time and your input. The two answers were very close and I could only accept only one of them. – AlefSin Dec 14, 2012 at 14:54

I had the same problem in an application I've been working on. It's exciting to read here about how the threadpool can bring it about.

Before I tracked down it's source though, I found that enclosing the contents of the DataReceived event handler in a try catch statement written in anticipation of the problem was a very effective way to solve it. Now that I know there's not really anything I can do to prevent the issue if I need/want to close a SerialPort while still receiving data, I'm quite happy with this approach.

I had similar issue when the user attempted to Exit application whilst it was still receiving data from the connected device. Application was throwing a System.IO.IOException following call to Me.Close().

Simplest solution I found was to set the SerialPort ReceivedBytesThreshold to a large number in the _FormClosing event handler. This reduces the frequency of DataReceived events and provides time for the Close() call to complete whilst the DataReceived event handler is inactive.

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.