Serial Port Data Received Event Handler - c#

I am reading data from a Serial Port in C# as follows:
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
If the function DataReceivedHandler is computationally intensive, is there a way to make sure that when the next data is received it doesn't wait for the previous function to complete, rather it starts another instances of DataReceivedHandler with the new data?

Hmm, I don't think that answer from Gh0st22 is concurrently sound. First off, the DataReceivedHandler is already called from a thread pool internal to the serial port class. Second, I see no locking implemented or mentioned. The serial buffer is going to be a nightmare to handle so let's step back a bit.
Are you actually observing the problem that you are afraid will occur? See this great response that I have referenced many times:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e36193cd-a708-42b3-86b7-adff82b19e5e/how-does-serialport-handle-datareceived?forum=netfxbcl#298028ff-2b76-4685-947c-f68291882b09
If you absolutely must spin up another thread, consider reading in the available serial data and passing it in as an argument. Otherwise you are just making a huge mess for yourself and any future maintainer on the project.

You can achieve this by using Threading (add using System.Threading;)
public static void main(string[] args)
{
SerialPort mySerialPort = new SerialPort();
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
}
public static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
Thread thread = new Thread(thisClass.Worker);
thread.Start(sender, e);
}
public static void Worker(object sender, object ev)
{
SerialDataReceivedEventArgs e = (SerialDataReceivedEventArgs) ev;
// Put your code here
}
Advantages:
When this method is called while work is being done, it will be allowed to finish
Drawbacks:
The program will continue running, even after its closed, until all threads are finished

Related

How can I implement (WaitUntilEvent) in c#?

I have looked at the answer in
How to block until an event is fired in c#
unfortunately, it does not provide a full answer that I can incorporate or make it work for my situation.
I am building an app that talks to a remote equipment through Serial port.
There is a protocol to be used whenever communication is established. It goes like this:
PC: Are you ready?
Hardware: Yes go ahead!
PC: Here is my question <insert question here>
PC: Did you get it?
Hardware: Yes, I got your question. Let me think
...
some time later
...
Hardware: Are you ready?
PC: yes, go ahead
Hardware: Here is your answer: <insert answer here>
I am currently using Sleep(1000) in-between messages to make sure the hardware did receive the message. But the problem is 1 second might be too much in a lot of cases. Because the hardware can reply as soon as 20 milliseconds, and at worst 800 milliseconds. I need to trigger an event whenever the hardware says it is ready, or when it asks if PC is ready.
Here is what I am hoping to do:
private event EventHandler HardwareSaysItIsReadyEvent;
private event EventHandler HardwareAsksIfPcIsReadyEvent;
static async Task Main(string[] args)
{
SetUpComPort();
HardwareSaysItIsReadyEvent += HardwareIsReadyEvent;
HardwareAsksIfPcIsReady += IsPcReadyEvent;
await AskHardwareIfReadyAndWaitForAnswer();
// assume the hardware will always answer with YES, but it may take time
await AskHardwareQuestion();
await AskHardwareIfQuestionWasReceivedAndWaitForAnswer();
}
private void SetUpComPort() { ... }
private async Task AskHardwareIfReadyAndWaitForAnswer()
{
/* I can send data to the hardware but how can I force
The process to wait here until the event is triggered,
or 1000 milliseconds expire?
*/
}
private async Task AskHardwareIfQuestionWasReceivedAndWaitForAnswer() { ... }
private void HardwareIsReadyEvent(object sender, EventArgs e)
{
// let the awaiter know so it may proceed to the question part
}
private void IsPcReadyEvent(object sender, EventArgs e)
{
// send a YES confirmation then let awaiter know so that it may proceed to receive answer
}
How can I suspend execution (without blocking UI) until a certain timeout, or an event is triggered?
Edit: I know how to trigger events based on text received from hardware, I did not show that part in my code. I don't need help triggering the event. I just need help telling program to send something and waiting for a reply or quitting after 1 second.
Edit 2: I am using SerialPort builtin class to do the talking with the hardware, and am already hooking up data received event in case the hardware is talking to the PC. That is how I am firing the events ServerSaysItIsReady and ServerIsAsking...
If your process is sequential (only one "question" at a time, always followed by event) then you can use single TaskComletionSource to achieve your goal:
private TaskCompletionSource _reply;
private async Task AskHardwareIfReadyAndWaitForAnswer() {
_reply = new TaskCompletionSource();
// now send a message to the hardware
// use separate task to indicate timeout
var timeoutTask = Task.Delay(1000);
// if timeoutTask completed first - then you waited for 1 second with no reply
bool isTimeout = await Task.WhenAny(_reply.Task, timeoutTask) == timeoutTask;
if (isTimeout) {
_reply = null;
}
}
private void HardwareIsReadyEvent(object sender, EventArgs e) {
// signal
_reply?.TrySetResult();
}

C# System.Windows.Forms.Timer EventHandler not being called

Part of my program receives input over a network connection, and sends a message back. I want to limit the number of times a certain input can trigger a message, so the program can't be overloaded.
I have a background worker that waits for the input, and then when it receives the certain input, it calls into a static class that will determine if it has been enough time since the last input. I'm using a
System.Windows.Forms.Timer
To do this. It looks like this (everything is public so I can debug):
public static class InputResponse
{
public static System.Windows.Forms.Timer Time = new System.Windows.Forms.Timer();
public static void CreateTimer()//set all the properties of the timer
{
Time.Interval = 3000;
Time.Tick += new EventHandler(Time_Tick);
}
public static void InputAReceived()
{
if (Time.Enabled) //if the timer is running, do nothing
return;
else
{
//send response here
Time.Start();
}
}
public static void Time_Tick(object sender, EventArgs e)
{
Console.WriteLine("Time_Tick");
Time.Stop();
}
}
The problem is, the Time_Tick method never gets called from the timer. I can use Invoke() to trigger the method like so,
EventHandler testHandler = new EventHandler(InputResponse.Time_Tick);
testHandler.Invoke(sender, e);//triggered by a button
which writes to the console like it should, but just waiting for the timer doesn't work. It will send the response once, and then won't send it again, since the timer never gets stopped.
The ridiculous thing is I have it working almost exactly the same in another class. The only difference is that the timer is constantly running.
What am I missing?
One problem with your code is that it is using the System.Windows.Forms.Timer class from a background thread and not associated with a window. This violates the instructions given in the documentation:
This timer is optimized for use in Windows Forms applications and must be used in a window.
For timers not related to GUI objects, use System.Timers.Timer.
This may or may not be the cause of the problems you're having, but it's one thing you'll need to address for your code to work correctly.

thread sleep blocking serial port data event handler

I am reading data through serial port which is working correctly. Following is my code in short
public Form1()
{
InitializeComponent();
//Background worker
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
connectComPort.DataReceived += new SerialDataReceivedEventHandler(receiveData);
enableDisable();
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
backProcess();
m_oWorker.ReportProgress(100);
}
private void backProcess()
{
//do some work
Thread.Sleep(10000);
if(check if 2000 bytes received)
{
//do some work
}
}
backProcess() is running on background worker and I have a global queue in which I insert bytes received from serial port and I check this queue in if loop.
My problem is that when 2000 bytes are send from other end to pc I receive less than 1000 bytes till after thread.sleep statement but if I set a breakpoint at thread.sleep step I receive complete 2000 bytes. So does thread.sleep(background thread) blocks data receive event handler also? How can I avoid this problem?
Some things don't get quite clear from your question, but I think your design is flawed.
You don't need a background worker at all and you don't need to sleep some thread.
The best way to handle serial input is to use the already asynchronous DataReceived event of the SerialPort class, which is called whenever there's data to be read (you're already doing this, as far as I can tell from your latest edit).
You can then read the existing data, append it to a StringBuilder (or fill a list of up to 2000 bytes) and launch whatever you want to do from there.
Pseudo-Code example would be:
DataReceived event
1. Read data (using `SerialPort.ReadExisting`)
2. Append Data to buffer, increase total number of bytes read
3. If number of bytes >= 2000: Spawn new thread to handle the data
BackgroundWorker is NOT the right tool for this, by the way! If handling the 2000 bytes is fast enough, you don't even need to spawn a new thread at all.

SerialPort fires DataReceived event after close

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.
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.
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.
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.

How do I transmit over the serial port asynchronously in .NET?

I am writing a program to simulate a device that transmits data over the serial port. To do this, I created a System.IO.Ports.SerialPort object in my form, and a System.Windows.Forms.Timer to do the transmitting at a given frequency. Everything works fine except that as the frequency approaches the limit of the serial port speed, it starts to lock up the UI and eventually becomes unresponsive when the data is being sent for transmission faster than the port data speed. My code is:
private void OnSendTimerTick(object sender, EventArgs e)
{
StringBuilder outputString = new StringBuilder("$", 51);
//code to build the first output string
SendingPort.WriteLine(outputString.ToString());
outputString = new StringBuilder("$", 44);
//code to build the second output string
SendingPort.WriteLine(outputString.ToString());
if (SendingPort.BytesToWrite > 100)
{
OnStartStopClicked(sender, e);
MessageBox.Show("Warning: Sending buffer is overflowing!");
}
}
I was expecting the WriteLine function to be asynchronous - return immediately while the port transmits in the background. Instead, it seems that the OnSendTimerTick function is properly threaded, but WriteLine seems to be running in the UI thread.
How can I get the serial port to behave in this way? Creating the SerialPort object in the timer seems like a bad idea, because then I'd have to open and close it on each timer tick.
It is only partly asynchronous, it will immediately return but only as long as the bytes you write will fit in the serial port driver's transmit buffer. That's going to come to a screeching stop when you flood the port with too much data, faster than it can transmit. You can make it truly asynchronous by using the SerialPort.BaseStream.BeginWrite() method. That doesn't make it any faster but moves the bottleneck somewhere else, possibly away from the UI.
If you're using System.Windows.Forms.Timer, the timer event handler is being executed on the UI thread. Same is true if you're using System.Timers.Timer when the SynchronizingObject is set to the form. If the serial port buffer fills up, the thread has to wait until it has enough space to hold the new data that you want to send.
I would suggest that you use a System.Threading.Timer to do this. The timer callback is called on a pool thread, meaning that the UI thread won't lock up if the WriteLine has to wait. If you do this, then you have to make sure that there is only one threading executing the timer callback at any time. Otherwise you can get data out of order. The best way to do that would be to make the timer a one-shot and re-initialize it at the end of every callback:
const int TimerFrequency = 50; // or whatever
System.Threading.Timer timer;
void InitTimer()
{
timer = new System.Threading.Timer(TimerCallback, null, TimerFrequency, Timeout.Infinite);
}
void TimerCallback(object state)
{
// do your stuff here
// Now reset the timer
timer.Change(TimerFrequency, Timeout.Infinite);
}
Passing a valid of Timeout.Infinite as the period parameter prevents the timer from being a periodic timer. Instead, it fires just once. The Timer.Change re-initializes the timer after each send.
A possibly better way to handle this is to eliminate the timer altogether by setting the WriteBufferSize to a sufficiently large value. Then your program can just dump all of its data into the buffer and let the SerialPort instance worry about dribbling it out across the wire. This assumes, of course, that you can create a buffer large enough to hold whatever your program is trying to send.
This could be resolved(slowness of the UI) if you created a Queue of strings to be written and had a background thread that wrote to the serial port from the queue. If you take that approach be careful of the size of the queue.
edit: For some reason I can't use Add Comment, so I'll just edit this. The documentation for BeginWrite has this statement "The default implementation of BeginWrite on a stream calls the Write method synchronously, which means that Write might block on some streams." It then goes on to exclude File and Network streams, but not SerialPort. I guess you can try it and see.

Categories

Resources