using invoke in a class rather than in a form - vb.net

i wrote a program reading a com port for a signal, everything was working fine, but they wanted to make the application a service so i swapped the application type to 'windows service' and created a class and put everything in the form in there and i called the class in my Main() in the startup module. the line,
Me.Invoke(New myDelegate(AddressOf UPdateVariable), New Object() {})
in the class has invoke in red saying that, "'Invoke, is not a member of Moisture.Moisture.'" and the "Me" part of that line is no longer greyed out as it was in the form. it worked before dont know what made the difference.
this is the whole code for that class
Imports System
Imports System.IO.Ports
Imports System.Net.Mime
Public Class Moisture
Dim WithEvents serialPort As New IO.Ports.SerialPort
Public Delegate Sub myDelegate()
Public RawString As New System.Text.StringBuilder
Public value As String
Public Sub StartListening()
If serialPort.IsOpen Then
serialPort.Close()
End If
Try
With serialPort
.PortName = "COM3"
.BaudRate = 9600
.Parity = Parity.None
.StopBits = StopBits.One
.DataBits = 8
.Handshake = Handshake.None
.RtsEnable = True
End With
serialPort.Open()
Catch ex As Exception
End Try
End Sub
Private Sub serialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
Me.Invoke(New myDelegate(AddressOf UPdateVariable), New Object() {})
End Sub
Public Sub UPdateVariable()
With RawString
.Append(serialPort.ReadLine())
End With
If RawString.ToString().Count(Function(x As Char) x = "%"c) = 2 Then
PiTagUpdater(StringParser(RawString.ToString()))
RawString.Clear()
End If
End Sub
Public Function StringParser(RawString As String) As String
Dim Moisture = RawString
Dim value As String
Dim values As String() = Moisture.Split(New Char() {":"c})
value = values(values.Length - 1).Trim({" "c, "%"c})
Return value
End Function
Private Sub PiTagUpdater(Value As Decimal)
Try
Dim piserver As New PCA.Core.PI.PIServer(PCA.Core.Globals.Applications.Application("GENERAL").ConfigValues.ConfigValue("PI_SERVER_NAME").StringValue, PCA.Core.Globals.Applications.Application("GENERAL").ConfigValues.ConfigValue("PI_SERVER_UID").GetDeCryptedStringValue, PCA.Core.Globals.Applications.Application("GENERAL").ConfigValues.ConfigValue("PI_SERVER_PASSWD").GetDeCryptedStringValue, True)
Dim TimeStamp As DateTime = FormatDateTime(Now)
Dim RapidRingCrush = "M1:RapidRingCrush.T"
Try
piserver.WriteValue(RapidRingCrush, Value, TimeStamp)
Catch ex As Exception
MessageBox.Show("Error occured locating Pi Tag", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Application.Exit()
End Try
Catch ex As Exception
MessageBox.Show("Cannot connect to Pi Server")
End Try
End Sub
End Class

Me refers to the current object, in this case the instance of your class. Your class doesn't have an Invoke() method (like the error says), though anything that derives from System.Windows.Forms.Control does (for instance a Form).
Control.Invoke() is used to move the execution of a method to the same thread that the control was created on. This is used to achieve thread-safety.
Since you switched to a service it is fair to assume that you do not have a user interface, thus there's no need to invoke. Removing the Invoke() call and calling the method manually should be enough:
Private Sub serialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
UPdateVariable()
End Sub
EDIT:
As LarsTech says, you also have to switch the message boxes to some kind of logging methods. A Windows Service usually runs under a different account, which means that it cannot display a user interface to the current user.

Related

VB.net - How can a definition at module level throw an exception

I was beginning to think that I was getting good at VB.net, but not this one has me stumped.
Code looks something like this
Public Class MyServer
.....
Public myMQTTclient = New MqttClient("www.myserv.com")
.....
Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles
MyBase.Load
<some code>
StartMQTT()
<some more code>
MQTT_Publish(.....)
End Sub
Public Function StartMQTT()
' Establish a connection
Dim code As Byte
Try
code = myMQTTclient.Connect(MQTT_ClientID)
Catch ex As Exception
<error handling code>
End Try
Return code
End Function
Public Sub MQTT_Publish(ByVal DeviceID As String, ByVal Channel As String, ByVal ChannelType As String, ByVal Value As String, ByVal Unit As String)
Dim myTopic As String = "MyTopic"
Dim myPayload As String = "My Payload"
Dim msgId As UShort = myMQTTclient.Publish(myTopic, Encoding.UTF8.GetBytes(myPayload), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, False)
End Sub
As this stands it works 100% OK. The coding may seem a bit odd, but the intent is as follows :
a) create an object 'myMQTTclient' at module level so it has scope throughout the module
b) run StartMQTT() - It can still see the object.
c) within main program call MQTT_Publish many times - I can still see the object
Now the issue is this... it all goes well until "www.myserv.com" fails DNS, then the underlying winsock code throws an exception.
So ... I'm thinking - no problem - just wrap the declaration in a try block or check that www.myserv.com exists before launching the declaration.
Ah, but you can't put code at module level, it has to be in a sub or function.
Hmmm... now I'm stumped. There has to be a 'proper' way to do this, but I'll be darned if I can figure it out.
Anyone able to help ?
I'd follow the advice from #djv about declaring it just as you need it. To wrap that in a Try... Catch block you can do that in an Init method.
Public Class MyServer
Implements IDisposable ' As per djv recommendation which I second...
Private myMQQTclient As MqttClient
Public Sub Init()
Try
myMQQTClient = New MqttClient("<your url>")
Catch ex As Exception
' Do whatever
End Try
End Sub
' more code and implement the Dispose method...
End Class
You can then go on and implement the IDisposble interface to ensure that you release the resources.
Ok, I am not sure if I have the right library. But I found this Nuget package: OpenNETCF.MQTT which seems to have the class you are using.
I would do it this way
Public Class MyServerClass
Implements IDisposable
Public myMQTTclient As MQTTClient
'Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles MyBase.Load
' ' <some code>
' StartMQTT()
' ' <some more code>
' ' MQTT_Publish(.....)
'End Sub
Public Sub New(brokerHostName As String)
myMQTTclient = New MQTTClient(brokerHostName)
End Sub
Public Function StartMQTT()
' Establish a connection
Dim code As Byte
Try
code = myMQTTclient.Connect(MQTT_ClientID)
Catch ex As Exception
'<error handling code>
End Try
Return code
End Function
Public Sub MQTT_Publish(ByVal DeviceID As String, ByVal Channel As String, ByVal ChannelType As String, ByVal Value As String, ByVal Unit As String)
Dim myTopic As String = "MyTopic"
Dim myPayload As String = "My Payload"
Dim msgId As UShort = myMQTTclient.Publish(myTopic, Encoding.UTF8.GetBytes(myPayload), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, False)
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
myMQTTclient.Dispose()
End If
End If
disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
#End Region
End Class
And now you can see the usage when IDisposable is implemented, in a Using block:
Module Module1
Sub Main()
Using myserver As New MyServerClass("www.myserv.com")
myserver.StartMQTT()
myserver.MQTT_Publish(...)
End Using
End Sub
End Module
This makes it so your object is only in scope in the Using, and the object's Dispose method will automatically be called on End Using
I don't know what the base class was originally and why this was declared Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles MyBase.Load. It seems like it was possibly a form? You should keep your server code separate from Form code if that was the case. I suppose you could paste the Using into your form load, but then you would be blocking your UI thread. The referenced library has Async support so it might be a good idea to leverage that if coming from a UI.
I've made many assumptions, so I'll stop to let you comment and see how close relevant my answer is.

Accesing a class with properties from another class returns null values

I am making an application that reads values from a meter that is connected to my computer via a serial cable. When i press a button i send a command to the meter and after a few miliseconds i get a response back from the meter with the answer.
I am saving these values to a class that has properties init, so that i can access these values from anywhere.
So my problem is that when i try to get the values back it returns a 'nothing value', and its probably from the initialization i have that has a 'New' like this'Dim clsSavedValues As New clsSavedValues', so when i try to get the values from that property class i create a new instanse and that instanse is empty if am not mistaken.
Ill post the code below but here is how the code flows:
I have 3 classes. MainClass, ProtocolClass, PropertiesClass.
From main i call a method inside ProtocolClass, and that method sends a command to the meter. after a few miliseconds i get a call back inside ProtocolClass anf this method is called 'Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived' and it saves that return value to the PropertiesClass.
And after the DataReceived method is finished i go back to the MainClass and call another method to get the values from the PropertiesClass that i just saved but they return null. I know they are saved correctly because i can access them if i call them from within the ProtocolClass. But they are null from MainClass.
Here is my code:
MainClass
'Here i call the ProtocolClass
Private Sub btnGetLastTransaction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGetLastTransaction.Click
clsProtocol.GetLastTransaction(1, Integer.Parse(tbxTransactionPosition.Text))
End Sub
'Here i try to read the valies from PropertiesClass
Public Sub RetrieveMeterSerialNumber()
Dim clsSavedValues As New clsSavedValues
lblMeterSerialNumber.Text = clsSavedValues.SaveMeterSerialNumber
End Sub
ProtocolClass
Public Sub GetLastTransaction(ByVal destinationAddress As String, ByVal transactionNum As Integer)
clsSavedValues = New clsSavedValues 'Creating Instance of the properties class
Try
Dim v_bodyOfMessage As [Byte]() = {ASCIItoHEX("G"), _
ASCIItoHEX("r")}
Dim v_bytearray As [Byte]() = ConstructCommand(v_bodyOfMessage)
SendCommand(v_bytearray)
Catch ex As Exception
Console.WriteLine("Meter serial number button click exception: {0}", ex)
End Try
End Sub
Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
If comOpen Then
Try
ReDim rx(rxPacketSize)
Console.WriteLine("RESPONSE")
For i = 0 To rxPacketSize - 1
readByte = SerialPort.ReadByte.ToString
Console.WriteLine(i.ToString & ": " & Conversion.Int(readByte).ToString)
rx(i) = Conversion.Int(readByte).ToString
If i <> 0 Then
If Convert.ToByte(rx(i)) = vDelimeterFlag(0) Then Exit For
End If
Next
DecodeResponse()
Catch ex As Exception
MsgBox("SerialPort_DataReceived Exception: " & ex.Message)
End Try
End If
End Sub
Private Sub GetMeterSerialNumber()
Dim i_startPosition As Integer = 5
Dim meterSerialNumber As String = GetRemainingPortionOfString(i_startPosition)
clsSavedValues.SaveMeterSerialNumber = meterSerialNumber
frmExplorer.RetrieveMeterSerialNumber() 'This is the call to the main class
End Sub
PropertiesClass
Public Property SaveMeterSerialNumber() As String
Get
Return _MeterSerialNumber
End Get
Set(ByVal meterSerialNumber As String)
_MeterSerialNumber = meterSerialNumber
End Set
End Property
I want to get the values from the PropertiesClass because ill get more than wan response from the meter and that causes thread issues and i cannot keep track with them. So i save the values in one class and then i want to access them all from that class.
Sorry for the long post, ask me anything you want for clarification
clsSavedValues in SerialPort_DataReceived() and in your Main Class RetrieveMeterSerialNumber() are two different objects (with same variable names but each 'new' create a new instance of clsSavedValues ) maybe you should pass the clsSavedValues var from protocol to Main as parameter.
Main :
Public Sub RetrieveMeterSerialNumber(clsSavedValues As clsSavedValues )
lblMeterSerialNumber.Text = clsSavedValues.SaveMeterSerialNumber
End Sub
Protocol :
Private Sub GetMeterSerialNumber()
Dim i_startPosition As Integer = 5
Dim meterSerialNumber As String = GetRemainingPortionOfString(i_startPosition)
clsSavedValues.SaveMeterSerialNumber = meterSerialNumber
frmExplorer.RetrieveMeterSerialNumber(clsSavedValues) 'This is the call to the main class
End Sub
or use a static property in your PropertiesClass

Is there away to switch from a Worker Thread to the Main (UI) thread?

I apologize in advance if my question is too long-winded. I looked at the question “How to update data in GUI with messages that are being received by a thread of another class?” and it is very close to what I am trying to do but the answer was not detailed enough to be helpful.
I have converted a VB6 app to VB.NET (VS2013). The main function of the app is to send queries to a Linux server and display the results on the calling form. Since the WinSock control no longer exists, I’ve created a class to handle the functions associated with the TcpClient class. I can successfully connect to the server and send and receive data.
The problem is that I have multiple forms that use this class to send query messages to the server. The server responds with data to be displayed on the calling form. When I try to update a control on a form, I get the error "Cross-thread operation not valid: Control x accessed from a thread other than the thread it was created on." I know I’m supposed to use Control.InvokeRequired along with Control.Invoke in order to update controls on the Main/UI thread, but I can’t find a good, complete example in VB. Also, I have over 50 forms with a variety of controls on each form, I really don’t want to write a delegate handler for each control. I should also mention that the concept of threads and delegates is very new to me. I have been reading everything I can find on this subject for the past week or two, but I’m still stuck!
Is there some way to just switch back to the Main Thread? If not, is there a way I can use Control.Invoke just once to cover a multitude of controls?
I tried starting a thread just after connecting before I start sending and receiving data, but netStream.BeginRead starts its own thread once the callback function fires. I also tried using Read instead of BeginRead. It did not work well if there was a large amount of data in the response, BeginRead handled things better. I feel like Dorothy stuck in Oz, I just want to get home to the main thread!
Thanks in advance for any help you can provide.
Option Explicit On
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Friend Class ATISTcpClient
Public Event Receive(ByVal data As String)
Private Shared WithEvents oRlogin As TcpClient
Private netStream As NetworkStream
Private BUFFER_SIZE As Integer = 8192
Private DataBuffer(BUFFER_SIZE) As Byte
Public Sub Connect()
Try
oRlogin = New Net.Sockets.TcpClient
Dim localIP As IPAddress = IPAddress.Parse(myIPAddress)
Dim localPrt As Int16 = myLocalPort
Dim ipLocalEndPoint As New IPEndPoint(localIP, localPrt)
oRlogin = New TcpClient(ipLocalEndPoint)
oRlogin.NoDelay = True
oRlogin.Connect(RemoteHost, RemotePort)
Catch e As ArgumentNullException
Debug.Print("ArgumentNullException: {0}", e)
Catch e As Net.Sockets.SocketException
Debug.Print("SocketException: {0}", e)
End Try
If oRlogin.Connected() Then
netStream = oRlogin.GetStream
If netStream.CanRead Then
netStream.BeginRead(DataBuffer, 0, BUFFER_SIZE, _
AddressOf DataArrival, DataBuffer)
End If
Send(vbNullChar)
Send(User & vbNullChar)
Send(User & vbNullChar)
Send(Term & vbNullChar)
End If
End Sub
Public Sub Send(newData As String)
On Error GoTo send_err
If netStream.CanWrite Then
Dim sendBytes As [Byte]() = Encoding.UTF8.GetBytes(newData)
netStream.Write(sendBytes, 0, sendBytes.Length)
End If
Exit Sub
send_err:
Debug.Print("Error in Send: " & Err.Number & " " & Err.Description)
End Sub
Private Sub DataArrival(ByVal dr As IAsyncResult)
'This is where it switches to a WorkerThread. It never switches back!
On Error GoTo dataArrival_err
Dim myReadBuffer(BUFFER_SIZE) As Byte
Dim myData As String = ""
Dim numberOfBytesRead As Integer = 0
numberOfBytesRead = netStream.EndRead(dr)
myReadBuffer = DataBuffer
myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
Do While netStream.DataAvailable
numberOfBytesRead = netStream.Read(myReadBuffer, 0, myReadBuffer.Length)
myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
Loop
'Send data back to calling form
RaiseEvent Receive(myData)
'Start reading again in case we don‘t have the entire response yet
If netStream.CanRead Then
netStream.BeginRead(DataBuffer, 0,BUFFER_SIZE,AddressOf DataArrival,DataBuffer)
End If
Exit Sub
dataArrival_err:
Debug.Print("Error in DataArrival: " & err.Number & err.Description)
End Sub
Instead of using delegates one could use anonymous methods.
Singleline:
uicontrol.Window.Invoke(Sub() ...)
Multiline:
uicontrol.Window.Invoke(
Sub()
...
End Sub
)
If you don't want to pass an UI control every time you need to invoke, create a custom application startup object.
Friend NotInheritable Class Program
Private Sub New()
End Sub
Public Shared ReadOnly Property Window() As Form
Get
Return Program.m_window
End Get
End Property
<STAThread()> _
Friend Shared Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Dim window As New Form1()
Program.m_window = window
Application.Run(window)
End Sub
Private Shared m_window As Form
End Class
Now, you'll always have access to the main form of the UI thread.
Friend Class Test
Public Event Message(text As String)
Public Sub Run()
Program.Window.Invoke(Sub() RaiseEvent Message("Hello!"))
End Sub
End Class
In the following sample code, notice that the Asynchronous - Unsafe run will throw a Cross-thread exception.
Imports System.Threading
Imports System.Threading.Tasks
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.cbOptions = New ComboBox() With {.TabIndex = 0, .Dock = DockStyle.Top, .DropDownStyle = ComboBoxStyle.DropDownList} : Me.cbOptions.Items.AddRange({"Asynchronous", "Synchronous"}) : Me.cbOptions.SelectedItem = "Asynchronous"
Me.btnRunSafe = New Button() With {.TabIndex = 1, .Dock = DockStyle.Top, .Text = "Run safe!", .Height = 30}
Me.btnRunUnsafe = New Button() With {.TabIndex = 2, .Dock = DockStyle.Top, .Text = "Run unsafe!", .Height = 30}
Me.tbOutput = New RichTextBox() With {.TabIndex = 3, .Dock = DockStyle.Fill}
Me.Controls.AddRange({Me.tbOutput, Me.btnRunUnsafe, Me.btnRunSafe, Me.cbOptions})
Me.testInstance = New Test()
End Sub
Private Sub _ButtonRunSafeClicked(s As Object, e As EventArgs) Handles btnRunSafe.Click
Dim mode As String = CStr(Me.cbOptions.SelectedItem)
If (mode = "Synchronous") Then
Me.testInstance.RunSafe(mode)
Else 'If (mode = "Asynchronous") Then
Task.Factory.StartNew(Sub() Me.testInstance.RunSafe(mode))
End If
End Sub
Private Sub _ButtonRunUnsafeClicked(s As Object, e As EventArgs) Handles btnRunUnsafe.Click
Dim mode As String = CStr(Me.cbOptions.SelectedItem)
If (mode = "Synchronous") Then
Me.testInstance.RunUnsafe(mode)
Else 'If (mode = "Asynchronous") Then
Task.Factory.StartNew(Sub() Me.testInstance.RunUnsafe(mode))
End If
End Sub
Private Sub TestMessageReceived(text As String) Handles testInstance.Message
Me.tbOutput.Text = (text & Environment.NewLine & Me.tbOutput.Text)
End Sub
Private WithEvents btnRunSafe As Button
Private WithEvents btnRunUnsafe As Button
Private WithEvents tbOutput As RichTextBox
Private WithEvents cbOptions As ComboBox
Private WithEvents testInstance As Test
Friend Class Test
Public Event Message(text As String)
Public Sub RunSafe(mode As String)
'Do some work:
Thread.Sleep(2000)
'Notify any listeners:
Program.Window.Invoke(Sub() RaiseEvent Message(String.Format("Safe ({0}) # {1}", mode, Date.Now)))
End Sub
Public Sub RunUnsafe(mode As String)
'Do some work:
Thread.Sleep(2000)
'Notify any listeners:
RaiseEvent Message(String.Format("Unsafe ({0}) # {1}", mode, Date.Now))
End Sub
End Class
End Class
Thank you to those who took the time to make suggestions. I found a solution. Though it may not be the preferred solution, it works beautifully. I simply added MSWINSCK.OCX to my toolbar, and use it as a COM/ActiveX component. The AxMSWinsockLib.AxWinsock control includes a DataArrival event, and it stays in the Main thread when the data arrives.
The most interesting thing is, if you right click on AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent and choose Go To Definition, the object browser shows the functions and delegate subs to handle the asynchronous read and the necessary delegates to handle BeginInvoke, EndInvoke, etc. It appears MicroSoft has already done the hard stuff that I did not have the time or experience to figure out on my own!

When to expect a `Cross-thread Exception`?

I'm having a hard time trying to understand what seems to be a random throwing of cross-thread exceptions.
Examples
When invoked in a different thread, why does this work:
Dim text As String = Me.Text
While this will throw an exception:
Me.Text = "str"
What makes it even stranger is that the following do work:
Dim text As String = Me.ctl.Margin.ToString() : Me.ctl.Margin = New Padding(1, 2, 3, 4)
Dim text As String = Me.ctl.MyProp : Me.MyProp = "str"
Note
Yes, I know that I could just invoke the property like this:
Me.Invoke(Sub() Me.Text = "str")
Question
So when can I expect a cross-thread exception?
Code
This is the code i used to test the Me.Text property:
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.ctl = New Control()
Me.ctl.Text = "test_control"
Me.Controls.Add(Me.ctl)
End Sub
Private Sub TestGet(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New Thread(AddressOf Me._Proc)
t.Start(TESTTYPE.GET)
End Sub
Private Sub TestSet(sender As Object, e As EventArgs) Handles Button2.Click
Dim t As New Thread(AddressOf Me._Proc)
t.Start(TESTTYPE.SET)
End Sub
Private Sub _Proc(tt As TESTTYPE)
Dim text As String = String.Empty
Dim [error] As Exception = Nothing
Try
If (tt = TESTTYPE.GET) Then
text = Me.ctl.Text
ElseIf (tt = TESTTYPE.SET) Then
Me.ctl.Text = "test"
End If
Catch ex As Exception
[error] = ex
End Try
Me.Invoke(Sub() Me._Completed(tt, text, [error]))
End Sub
Private Sub _Completed(tt As TESTTYPE, text As String, ByVal [error] As Exception)
If ([error] Is Nothing) Then
If (tt = TESTTYPE.GET) Then
MessageBox.Show(String.Concat("Success: '", text, "'"), tt.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information)
ElseIf (tt = TESTTYPE.SET) Then
MessageBox.Show("Success", tt.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
Else
MessageBox.Show([error].Message, tt.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Error)
End If
End Sub
Private ReadOnly ctl As Control
Private Enum TESTTYPE
[GET] = 0
[SET] = 1
End Enum
End Class
Edit
This will not throw an exception:
Public Event TestChanged As EventHandler
Public Property Test() As String
Get
Return Me.m_test
End Get
Set(value As String)
If (value <> Me.m_test) Then
Me.m_test = value
Me.Invalidate()
RaiseEvent TestChanged(Me, EventArgs.Empty)
End If
End Set
End Property
the main time a cross-thread exception occurs when you do something that would cause an event to fire from the non-UI thread that affects the UI thread; So reading a property can be fine, but writing the property of a control would cause it to repaint (at the very least), hence the exception.
Of course, other vendors may have used the exception for other scenarios where it is not safe to access from a different thread
So when can I expect a cross-thread exception?
Well, GUI in .Net are created in STA which means that only the thread that create the control can update it this has to do with Thread-safe concept. for this reasons when you start another thread and try to access the control which is owned by the main thread you will get an invalidOperationException
So when can I expect a cross-thread exception?
Really simple, when you access some function or property of a control, from a thread which do not have right to access it.
For example, in Window form application when you try to access the button placed on form from a non-ui thread, i.e. not the main thread, (and you have not set any flags manually to allow cross-thread operation)
EDIT As per comment, how can I know I can/can not access a getter/ setter of a property. Where are the access rights defined? you can always be on safe side by querying the control's InvokeRequired property in Windows
Okay, so I did some research myself, and it turns out that the exception originates in the .Handle property of the Control.
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DispId(-515), SRDescription("ControlHandleDescr")> _
Public ReadOnly Property Handle As IntPtr
Get
If ((Control.checkForIllegalCrossThreadCalls AndAlso Not Control.inCrossThreadSafeCall) AndAlso Me.InvokeRequired) Then
Throw New InvalidOperationException(SR.GetString("IllegalCrossThreadCall", New Object() {Me.Name}))
End If
If Not Me.IsHandleCreated Then
Me.CreateHandle()
End If
Return Me.HandleInternal
End Get
End Property
System.Windows.Forms.resources:
IllegalCrossThreadCall=Cross-thread operation not valid: Control '{0}' accessed from a thread other than the thread it was created on.
So I believe the answer would be something like this:
Whenever the handle of a control is invoked from a different thread.
(I'll give you all a vote up as all the answers are relevant towards cross-threading.)
The following code will throw an exception:
Public Class Form1
Private Sub Test(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New Thread(AddressOf Me._Proc)
t.Start()
End Sub
Private Sub _Proc(id As Integer)
Dim [error] As Exception = Nothing
Try
Dim p As IntPtr = Me.Handle
Catch ex As Exception
[error] = ex
End Try
Me.Invoke(Sub() Me._Completed([error]))
End Sub
Private Sub _Completed(ByVal [error] As Exception)
If ([error] Is Nothing) Then
MessageBox.Show("Success", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information)
Else
MessageBox.Show([error].Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
Me.tbDescription.Text = [error].Message
End If
End Sub
End Class

Asynchronous socket library not updating form

I am attempted to write a small asynchronous socket library that I can use to create a client/server application. I can get all of the code to run fine if I leave it in the form, however if I try to move it out into its own class, I cannot figure out how to update the form with connection status, things like that. Below is code, shortened a bit just to make this easier to read and type.
Form Code:
Class Form1
Dim Network as NetworkModule
Public Sub Button1_Click(Sender, e) Handles Button1.Click
Network = New NetworkModule("127.0.0.1", 1234)
End Sub
End Class 'Form1
NetworkModule class:
Class NetworkModule
Private mSocket as Socket
Public Sub New(IP as string, Port as Integer)
Dim remoteEP as New IPEndpoint(IP.Parse(IP), Port)
mSocket = New Socket(internetwork, stream, tcp)
mSocket.BeginConnect(remoteEP, New AsyncCallback(AddressOf onConnect), mSocket)
Notify("Connection to " & remoteEP.ToString) 'This one works
End Sub 'New
Private Sub onConnect(ar as IAsyncResult)
mSocket = CType(ar.AsyncState, Socket)
mSocket.EndConnect(ar)
Notify("Connected") 'This one never shows
End Sub 'onConnect
Private Delegate Sub _Notify(Msg as String)
Private Sub Notify(Msg as String)
If Form1.txtLog.InvokeRequired Then
Form1.txtLog.Invoke(New _Notify(AddressOf Notify), Msg)
Exit Sub
End if
Form1.txtLog.Text &= Msg & vbcrlf
End Sub 'Notify
End Class 'NetworkModule
There is actually more to that class, but I never get anything after the first message goes out. I'm not sure where to go from here. I've tried lots of different methods that I've found on the google searches, some from here, some not. Anybody have any ideas?
Here's how I would rewrite it:
Class Form1
Private Network as NetworkModule
Public NotifyDelegate As NetworkModule.NotifyDelegate
Public Sub New()
NotifyDelegate = New NetworkModule.NotifyDelegate(AddressOf Notify)
End Sub
Public Sub Button1_Click(Sender, e) Handles Button1.Click
Network = New NetworkModule("127.0.0.1", 1234, Me)
End Sub
Public Sub Notify(Msg As String)
txtLog.Text &= Msg & vbCrLf
End Sub
End Class 'Form1
NetworkModule Class (partial):
Class NetworkModule
Public Delegate Sub NotifyDelegate(Msg as String)
Private Sub Notify(Msg as String)
If m_Form.InvokeRequired Then
m_Form.Invoke(Form1.NotifyDelegate, Msg)
Else
m_Form.Notify(Msg)
End If
End Sub 'Notify
Private mSocket as Socket
Private m_Form As Form1
Public Sub New(IP as string, Port as Integer, oForm As Form1)
m_Form = oForm
Dim remoteEP as New IPEndpoint(IP.Parse(IP), Port)
mSocket = New Socket(internetwork, stream, tcp)
mSocket.BeginConnect(remoteEP, New AsyncCallback(AddressOf onConnect), mSocket)
Notify("Connection to " & remoteEP.ToString) 'This one works
End Sub 'New
Update with Interface approach
A better mechanism than passing the form itself is to implement an interface. To do this, first create the interface definition (note that the delegate has moved to the Interface for convenience):
Public Interface INotify
Sub Notify(Msg As String)
Delegate Sub NotifyDelegate(Msg As String)
End Interface
Then Implement the interface in the Form. Note that the form now determines whether or not Invoke is required. This allows the INotify interface to be used in non-UI scenarios, such as logging to disk or the event log.
Public Class Form1
Implements INotify
Public Sub Notify(Msg As String)
txtLog.Text &= Msg & vbCrLf
End Sub
Private Sub INotify_Notify(Msg As String) Implements INotify.Notify
If Me.InvokeRequired Then
Me.Invoke(New INotify.NotifyDelegate(AddressOf Notify), Msg)
Else
Me.Notify(Msg)
End If
End Sub 'Notify
Private Network As NetworkModule
Public Sub Button1_Click(Sender, e) Handles Button1.Click
Network = New NetworkModule("127.0.0.1", 1234, Me)
End Sub
End Class 'Form1
Finally, store a reference to the INotify interface instead of the Form in NetworkModule (note the NetworkModule no longer needs to know or care that an Invoke may be required):
Public Class NetworkModule
Public Delegate Sub NotifyDelegate(Msg As String)
Private m_Notifier As INotify
Private Sub Notify(Msg As String)
m_Notifier.Notify(Msg)
End Sub 'Notify
Public Sub New(IP As String, Port As Integer, oNotifier As INotify)
m_Notifier = oNotifier
' The addition code here
End Sub 'New
End Class