DataReceived Event and Delegates reference
Hi All,
New VB2005 user here.
I'm looking for a reference that can explain the concept and use of the DataReceived event and Delegates subject.
The reference has to be pretty simple as I'm an "old dog attempting to learn a new trick". I'm not ignorant of things programming, but am ignorant of things dealing with serial communication in VB2005.
I already have a couple of O'Rielly books ("VB2005 Cookbook", "VB2005 Desktop Reference") and I have a book that deals with serial communication from a Jan Axle-something. Neither O'Rielly books have any info, and the Axle-someting book's remotely related examples are too confusing for this slow learner.
Thanks much,
DJ
Re: DataReceived Event and Delegates reference
Hi,
I describe it in some detail in my book, Visual Basic Programmer's Guide to Serial Communication 4. You can go to my homepage for more information (the VBTerm2005 example code -- Software Downloads- there shows one way to implement it; not the only way, BTW). This also may be helpful: http://visualstudiomagazine.com/arti...nd-the-ui.aspx.
Dick
Re: DataReceived Event and Delegates reference
Ok, basically, your program runs on a single processing thread, thus, it can only do a single thing at a time. When your program starts, Windows creates a new processing thread, and tells it where to start processing; which most likely is to start creating your primary form and all the control's on it.
.NET remembers which thread created the controls on your form and will only let THAT thread make changes to them. So if you have a TextBox on your form, and it was created by (most likely) your main application thread, then only your main application thread is allowed to change it's Text property.
See, threads all run at their own pace. It's up to Windows to give them processing time or not; thus, there's no guarantee that one thread will run before or after another one.
Now that that's out of the way, more specifically about your question. The SerialPort control. This control kinda operates on two threads at once. The thread that creates it, and a separate thread that handles data receival. You can use the thread that created it to do 99% of the things you need with it; open and close it, change it's settings and sending data.
The problem is, the receival of data is handled separately. It does this because data can come in at ANY time, and it doesn't want to ever interrupt your main application thread when it does; especially if it's in the middle of something! So, it uses a second thread.
This is all fine in programming until you need to do something like put that data to a TextBox on your form. Remember how controls can only be altered by the threads that created them? This is the problem. When the DataReceived event triggers and runs, it's running on a different thread than your main application thread. It can't directly tell the TextBox to set it's Text property to something it has.
This is where Delegates come in. Delegates are like "hand-me-offs". They allow threads to pass data back and forth. The DataReceived thread creates one and hands it it's data and which Sub/Function to use it in, then tells the main application thread "Hey, I got a delegate waiting to be processed". This is called Invoking.
In it's own time, the main application thread grabs the delegate and runs it.
That's about as layman as I can get on this topic. :)
Here's an example to further explain it:
Code:
Public Class Form1
'My serial port with events already hooked in. The wonders of Visual Basic's rocking event handling. :)
Private WithEvents serial As New IO.Ports.SerialPort
'This is my Delegate. It's the box that'll carry the String data from the receival thread to the main one.
Private Delegate Sub UpdateTextboxDelegate(ByVal myText As String)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'When the main application thread finished building the form, it runs this and configures and opens our serial port.
serial.BaudRate = 9600
serial.PortName = "COM1"
serial.Open()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'When the button is pressed, the main application thread sends "HELLO!" down our serial port.
serial.WriteLine("HELLO!")
End Sub
Private Sub serial_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles serial.DataReceived
'When this event happens, it's being processed by a second thread; one that's automatically created by the serialport
'object.
Dim myResponse As String = serial.ReadLine
UpdateTextbox(myResponse)
End Sub
Private Sub UpdateTextbox(ByVal myText As String)
'First, check if the control we're updating requires an invoke.
'This is just a simple test of the thread that's calling the function vs. the thread that created the control.
If Me.TextBox1.InvokeRequired Then
'This means the thread that's calling this function is NOT the one that created the TextBox.
'So, it makes a new delegate and tells it to send the data right back to this same function.
Dim d As New UpdateTextboxDelegate(AddressOf UpdateTextbox)
'This tells the main application thread that it's waiting for it to process. The data received thread is now complete.
Me.TextBox1.Invoke(d, New Object() {myText})
Else
'When the main application threas picks up the delegate, it'll run "UpdateTextbox" with the data it was handed.
'So, it'll pass the InvokeRequired test since the main application thread IS the thread that made the Textbox as well,
'thus, it'll just set the value as you'd expect it to.
TextBox1.Text = myText
End If
End Sub
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
'Don't forget to close your port when shutting down!
If serial.IsOpen Then serial.Close()
End Sub
End Class
Re: DataReceived Event and Delegates reference
@Mr. Grier - thank you, I'll go check it out.
@Jenner - quite a detailed response, thank you. "layman" is good! Give me a couple days to digest it all and I'll be back w/more questions.
BTW - what does "marshal" mean in VB context?
Regards,
DJ
Re: DataReceived Event and Delegates reference
Quote:
Originally Posted by
DeeJay95118
what does "marshal" mean in VB context?
Marshaling is moving data from one environment to another, e.g. moving from one thread context to another, moving from managed code to unmanaged code or vice versa.
Re: DataReceived Event and Delegates reference
@jmcilhinney - thanks!
DJ
Re: DataReceived Event and Delegates reference
@Jenner,
I took a day off my day job to work exclusively on my project and its serial communication aspect. I've entered, built, and run your example code. It appears to work just as you'd hoped.
Now I have to pick up the pieces of my imploded head and attempt to digest all that's in the code.
More questions on the way - thanks again for your help,
DJ
Re: DataReceived Event and Delegates reference
Thank you for your smart explanation: very useful and clear.
Re: DataReceived Event and Delegates reference
Hi,
I'm trying to implement this same strategy but I've got an issue with project references.
In my case, my Solution is divided in 3 layers: UI project, Logic project and Data Management (DM) project. The UI project references the Logic and DM ones, and the Logic references DM as well.
I have a custom class for serial port implemented in the DM project, with an instance of my custom-port created there that can be accessed by both UI and Logic on demand. This is working fine for data sending.
The problem arises when I try to implement the receiving strategy on the DataReceived Event as proposed here, because I can't call the 'UpdateUI' method from the DataReceived method as the reference between projects is in the other way.
Please, may you help me trying to figure out how to implement the Delegate-approach in this situation?
Thank you very much!
Re: DataReceived Event and Delegates reference
@mrwengineer, if you want to be able to marshal to the UI thread in code that is not UI-aware, use the SynchronizationContext class. Follow the CodeBank link in my signature below and check out my thread on Accessing Controls From Worker Threads. You'll find a later post dedicated to that.
Re: DataReceived Event and Delegates reference
Quote:
Originally Posted by
jmcilhinney
@mrwengineer, if you want to be able to marshal to the UI thread in code that is not UI-aware, use the SynchronizationContext class. Follow the CodeBank link in my signature below and check out my thread on Accessing Controls From Worker Threads. You'll find a later post dedicated to that.
Hi John,
Thank you for your advice. I have read the whole post and I got the point of the SynchronizationContext class. However still I can't figure out how to implement it in my solution.
In the UI project, I declared the 'context' as indicated, Private context As Threading.SynchronizationContext = Threading.SynchronizationContext.Current. Then in your example in the referenced post
HTML Code:
https://www.vbforums.com/showthread.php?498387-Accessing-Controls-from-Worker-Threads&highlight=
you fire the event twice, first through a control (button click) on the main form, and then through a backgroundworker, that is started in the click button as well, just for the different-thread execution demonstration purpose.
But my question is how I can adapt the SynchronizationContext use to handle a method (DataReceived) that is fired not only in another thread, but also in another project (in the Data Management layer, inside a CustomSerialPort class).
By the way I also tried what is suggested by user Sunglasses here
HTML Code:
https://www.vbforums.com/showthread.php?477650-2005-Serial-port-DataReceived-event
in the very last post of the thread, but got stucked again when adding the handling event in the UI project, because I can't handle an event on another project (or can I?).
Thank you again for your help.
Re: DataReceived Event and Delegates reference
As I said, you use the SynchronizationContext class in code that is NOT UI-aware. That means that you use in your business logic layer, not in your presentation layer. The presentation layer IS UI-aware, it being the UI and all. The idea is that you use the SynchronizationContext inside a class that does not inherit control to do the same job as the Invoke method of a control i.e. to invoke a method on the same thread that the object was created on. If that class is then used by your UI, an instance is created on the UI thread and so the SynchronizationContext will ensure a method is called on the UI thread. E.g.
vb.net Code:
Public Class SerialPortWrapper
Private WithEvents port As SerialPort
Private ReadOnly context As SynchronizationContext = SynchronizationContext.Current
'This event handler will be executed on a secondary thread.
Private Sub port_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles port.DataReceived
Dim text = port.ReadExisting()
context.Post(AddressOf RaiseSerialPortDataReceived, text)
End Sub
'This method will be executed on the UI thread.
Private Sub RaiseSerialPortDataReceived(text As Object)
OnSerialPortDataReceived(New SerialPortDataReceivedEventArgs(CStr(text)))
End Sub
Public Event SerialPortDataReceived As EventHandler(Of SerialPortDataReceivedEventArgs)
Protected Overridable Sub OnSerialPortDataReceived(e As SerialPortDataReceivedEventArgs)
RaiseEvent SerialPortDataReceived(Me, e)
End Sub
End Class
Public Class SerialPortDataReceivedEventArgs
Inherits EventArgs
Public ReadOnly Property Text As String
Public Sub New(text As String)
Me.Text = text
End Sub
End Class
It doesn't matter what project the SerialPortWrapper class is declared in. If a form creates an instance of that class on the UI thread, it can be assured that the SerialPortDataReceived event will be raised on the UI thread. That's the point. The SerialPortWrapper is not UI-aware but it can still marshal a call to the UI thread.
Re: DataReceived Event and Delegates reference
Quote:
Originally Posted by
jmcilhinney
It doesn't matter what project the SerialPortWrapper class is declared in. If a form creates an instance of that class on the UI thread, it can be assured that the SerialPortDataReceived event will be raised on the UI thread. That's the point. The SerialPortWrapper is not UI-aware but it can still marshal a call to the UI thread.
OK, I've the class already defined as you suggested. I think my problem is with the fact that the instance of my CustomSerialPort class is created in the Data layer, not in the UI layer, so I still can't access the new SerialPortDataReceived event raised in the UI thread.
The reason why I declared the instance of the CustomSerialPort in the Data layer is because I need also the port to be accessed by the Business Logic layer in other to send data, so by declaring it in the Data layer the port can be accessed both by the Presentation and the Logic layers.
Does it mean I am forced to declare the instance of the CustomSerialPort in the Presentation layer, or is there another way to keep it as it is now in order to mantain the Business Logic layer access needs?
Thank you very much again for your advice.
Re: DataReceived Event and Delegates reference
I've already shown you how one object can expose an event of another object via its own proxy event. In my example, the SerialPortWrapper object raises its own event when the SerialPort object it has access to raise its DataReceived event. That way, an object that has access to a SerialPortWrapper can be notified when a SerialPort it doesn't know exists raises an event. You can do exactly the same thing through as many layers as necessary. If your presentation layer object has a reference to a business layer object that has a reference to a data layer object that has access to a SerialPort, the DL object can raise and event when the SerialPort does and BL object can raise an event when the DL object does and the PL object can handle the event of the BL object. Congratulations! Your PL object was just notified when a SerialPort raised an event.
Alternatively, you could create a delegate in the PL object and pass it down as far as you wanted to. For instance, you could write a method that looked like a standard SerialPort.DataReceived event handler, but without the Handles clause, in your PL object. The PL object could then pass that, either as a method argument or property value, to the BL object and that could pass it to the DL object. The DL object can then use AddHandler to attach that delegate directly to the DataReceived event of the SerialPort object. That means that, when the SerialPort raises its DataReceived event, it will directly invoke a method in the PL object.
Re: DataReceived Event and Delegates reference
Finally I figure out how to implement it in my solution and it's working. I leave here the missing/complementary code to do the trick, just for reference if it helps another user.
As John suggested in the previous post, I added an object in the UI layer that references the object in the DL layer, so when the event is fired by the DL layer object, it propagates through the UI layer. Here's the code I added in the UI layer main form class to John's code, which in my solution is in the DL layer:
Code:
Public Class formMainUI
Private WithEvents portUI As DL.SerialPortWrapper
Private Sub formMainUI_Load(sender As Object, e As EventArgs) Handles MyBase.Load
portUI = DL.portDL
End Sub
Private Sub OnSerialDataReceivedUpdateUI(sender As Object, e As DL.SerialPortDataReceivedEventArgs) Handles portUI.SerialPortDataReceived
TextBox1.Text = e.Text
End Sub
End Class
Once again, thank you for your advice. Just a very final request: may you explain why is it necessary to split the code between the private sub RaiseSerialPortDataReceived and the protected and overridable sub OnSerialPortDataReceived to raise the SerialPortDataReceived event? Thank you very much.
Re: DataReceived Event and Delegates reference
Quote:
Originally Posted by
mrwengineer
Just a very final request: may you explain why is it necessary to split the code between the private sub RaiseSerialPortDataReceived and the protected and overridable sub OnSerialPortDataReceived to raise the SerialPortDataReceived event? Thank you very much.
It is not strictly required but it is good practice to do so. It is convention that each event have a dedicated method in a specific format to raise it. That format is as follows:
1. Protected and Overridable, so it is not callable from outside the object but it is callable from inside derived classes, which can add their own code to be executed when the event is raised.
2. A Sub, so no value is returned.
3. Named after the event itself with the prefix 'On' added.
4. Has a single parameter named 'e' that is type EventArgs or an appropriate derived class.
On the other hand, the Send and Post methods of the SynchronizationContext class require a delegate of type SendOrPostCallback, so the method you use there must have a single parameter of type Object. Those two signatures are incompatible so you cannot do both in a single method.