How do I call a function on the main thread from a

2019-09-10 02:34发布

问题:

I have a timer function which is called on my windows form which works perfectly when a button on the UI is clicked. However, in my application Im connecting to a server using a TCP socket, when the server disconnects Im catching the exception thrown and Id like at this point for the timer to start (ticker) then run the reconnection. When i reference the timer from the try catch it wont run, so I imagine its because its on the main thread?

Here is my code for the timer: Public Delegate Sub DroppedConnectionDelegate(ByVal ReConn As Integer)

 Public Sub DroppedConnection(ByVal ReConn As Integer)

    Console.Write("Dropped Connection()")

    Thread.Sleep(1000)
    If ReConn = 1 Then

            MakeConnection(My.Settings.savedIP, False)
        Else
            isRunning = False
            Second = 0
            'client.Close()
            'client.Dispose()
            Timer1.Interval = 1000
            Timer1.Enabled = True

            Timer1.Start() 'Timer starts functioning


        End If
    '  End If

End Sub

Public Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick

    Second = Second + 1
    Console.WriteLine(Second)
    If Second >= 10 Then
        Timer1.Stop() 'Timer stops functioning
        Timer1.Enabled = False
        MakeConnection(ServerAddressString, False)
    End If

End Sub

Here is the sub from where Id like to call the timer on catch exception:

 Public Shared Sub ReceiveCallback(ByVal ar As IAsyncResult)

        Dim state As StateObject = CType(ar.AsyncState, StateObject)
            Dim client As Socket = state.workSocket
            Dim strReceiveBuffer As String = String.Empty

            ' Read data from the remote device.
            Dim bytesRead As Integer

        Console.WriteLine("ar to string = " & ar.ToString)
        'While client.Connected
        If (AsynchronousClient.connectDone.WaitOne(5000, True)) Then
            Try

                If client.Connected = True Then

                    bytesRead = client.EndReceive(ar)
                    Console.WriteLine("Socket Connected")
                    If bytesRead > 0 Then
                        state.sb.Clear()
                        ' There might be more data, so store the data received so far.
                        state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead))

                        strReceiveBuffer = state.sb.ToString()
                        MainForm.main_Response = state.sb.ToString()

                        If strReceiveBuffer.IndexOf("doses-remaining") > 0 Then
                            response = state.sb.ToString()
                            MainForm.GetLabelTextToString(response)
                            'receiveDone.Set()
                            'isReceived = True
                            'Return
                        End If

                        ' Get the rest of the data.
                        client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)


                    Else
                        ' All the data has arrived; put it in response.
                        If state.sb.Length > 1 Then

                            response = state.sb.ToString()

                        End If
                        ' Signal that all bytes have been received.
                        receiveDone.Set()
                        isReceived = True
                    End If
                Else
                    MainForm.WriteLog("RecieveCallback() Error outside catch :" & Date.Today.ToString & " " & TimeOfDay)
                    MainForm.UpdateList("RecievecallBack error, attempting reconnect..." & client.RemoteEndPoint.ToString())

                    MainForm.isRunning = False
                    MainForm.DroppedConnection(0)
                End If


            Catch ex As Exception

                'MessageBox.Show("ReceiveCallback Error, Check Log.")

                MainForm.WriteLog("RecieveCallback() Error inside catch :" & Date.Today.ToString & " " & TimeOfDay & ex.Message)
                MainForm.UpdateList("RecievecallBack error, attempting reconnect..." & client.RemoteEndPoint.ToString())

                client.Shutdown(SocketShutdown.Both)
                client.Close()
                client.Dispose()
                MainForm.isRunning = False
                Dim d As DroppedConnectionDelegate = AddressOf MainForm.DroppedConnection
                'MainForm.DroppedConnection(0)
                d.BeginInvoke(0, Nothing, Nothing)
                Exit Try



            End Try

        End If
        ' End While
    End Sub 'ReceiveCallback

If I run the application and the server disconnects unexpectedly, currently it wont reconnect automatically. But it will run the timer if I click the connect button (which calls the timer anyway)

Can anyone help please?

Thanks in advance

回答1:

I'm not 100% sure as I've never tried but I think that if you use a Timers.Timer instead of a Windows.Forms.Timer then what you're doing now will work. By default, a Timers.Timer will raise its Elapsed event on a secondary thread but you can assign a form or other control to its SynchronizingObject property to make it raise events on the UI thread. Calling Start from a secondary thread should be OK in that case.

If that doesn't work then you can do what you want by first marshalling a method call to the UI thread before starting the Timer. That might look like this:

Private Sub StartTimer()
    If Me.InvokeRequired Then
        Me.Invoke(New Action(AddressOf StartTimer))
    Else
        Me.Timer1.Start()
    End If
End Sub

You can call that method on any thread and it will just work.

Note that setting the Enabled property and calling Start or Stop is redundant. Start already sets Enabled to True and Stop sets it to False. Do one or the other, not both. It is most appropriate to call a method when you know for a fact that you want to either start or stop the Timer. If you have a Boolean value that might be either True or False then you should use the property. Setting the property using a literal value is not wrong per se but it is not what was intended. An example of where it's appropriate to use the property is when you use the same method to both start and stop the Timer from a secondary thread:

Private Sub EnableTimer(enable As Boolean)
    If Me.InvokeRequired Then
        Me.Invoke(New Action(Of Boolean)(AddressOf EnableTimer), enable)
    Else
        Me.Timer1.Enabled = enable
    End If
End Sub

Again, calling that method on any thread will just work.