How do I call a function on the main thread from another thread in vb - vb.net

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

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.

Related

Textbox not updating after thread abort

I have a thread which is executing a sub and within a sub I am updating the textbox during a "Do while" loop is in progress. I then created a cancel button to abort the thread.
When the button is clicked then the thread is aborted but the textbox (Status_Txtbox.Text) doesn't get updated with the message "Parsing is terminated". I tried to do the debug and I see the code is executed perfectly and if condition for thread.isalive satisfies but not sure why the textbox doesn't get updated with the message.
Any idea how to update the textbox once thread is aborted ?
Dim thrd1 As Thread
Private Sub Parse_Btn_2G_Click(sender As Object, e As RoutedEventArgs) Handles Parse_Btn_2G.Click
Parse_Btn_2G.IsEnabled = False
Scan_Btn_2G.IsEnabled = False
cancel_Btn_2G.IsEnabled = True
Dim start As DateTime = DateTime.Now
Dim elapsedtime As Double
Dim action As Action
thrd1 = New Thread(Sub()
ButtonClickWork.DoWork()
action = Sub()
Status_Txtbox.Text = "Parsing Data, Please wait..."
End Sub
Me.Dispatcher.Invoke(action)
End Sub)
thrd1.Start()
elapsedtime = (DateTime.Now.Subtract(start).TotalSeconds) / 60
elapsedtime = Math.Round(elapsedtime, 2)
Status_Txtbox.Text = " Managed Objects in XML, total time elapsed is" & elapsedtime
End Sub
Private Sub Cancel_Btn_2G_Click(sender As Object, e As RoutedEventArgs) Handles cancel_Btn_2G.Click
Try
If cancel_Btn_2G.IsEnabled = True Then
If MsgBox("Do you really want to exit Parsing?", vbYesNo) = MsgBoxResult.Yes Then
Parse_Btn_2G.IsEnabled = True
Scan_Btn_2G.IsEnabled = True
cancel_Btn_2G.IsEnabled = False
thrd1.Abort()
thrd1.Join()
If thrd1.IsAlive = False Then
Status_Txtbox.Text = "Parsing is terminated"
End If
End If
End If
Catch ex As ThreadAbortException
Status_Txtbox.Text = "Parsing is terminated"
End Try
End Sub

Timers and variables in vb.net

I have a class with a timer. The timer starts in the new method. The method associated with the timer is checkMatch() but checkTimer() ensures the parameters are met to start the timer. They are met and the timer starts. The method checkMatch() is currently rewritten to only execute once since I know it keeps repeating itself. The only place to change the boolean variable oneTime is located inside of checkMatch(). I included the autoLoad() function just in case it is needed. Whenever I launch this class, the forms associated with the other methods not listed will pop up and instantly go away. The console shows that checkMatch() is being called regularly (which it should), but that the variable oneTime remains True each time it is called. The timer is started at the end of the autoLoad() method. I know all the methods in autoLoad() are being called and executed.
Since oneTime is showing as True constantly in the console, I am assuming I have a conception issue somewhere. Why is oneTime true each time checkMatch() is called?
' a user is required for this class
Public Sub New(ByVal my_user As RGOUser)
My.Settings.selected_summoner = "huge legend"
My.Settings.Region = "na1"
My.Settings.Save()
myUser = my_user
AddHandler tmr.Elapsed, AddressOf checkMatch
checkTimer()
End Sub
Public Sub checkMatch()
Console.WriteLine(Me.GetType.ToString() + "||" + System.Reflection.MethodInfo.GetCurrentMethod.ToString())
Console.WriteLine(oneTime)
Try
Dim psList() As Process = Process.GetProcesses()
For Each p As Process In psList
' If strLeagueProcessName = p.ProcessName) And Not boolInGame Then
' remove later and replace with commented line
If oneTime Then
Console.WriteLine("checkMatch Success.")
boolInGame = True
oneTime = False
tmr.Interval = timerInsideMatch
tmr.Stop()
Try
autoLoad()
Catch ex As Exception
End Try
Return
Else
Return
End If
Next
Catch ex As Exception
Console.WriteLine("checkMatch Failure. " + ex.Message)
End Try
boolInGame = False
tmr.Interval = timerOutsideMatch
End Sub
Private Sub autoLoad()
Console.WriteLine(Me.GetType.ToString() + "||" + System.Reflection.MethodInfo.GetCurrentMethod.ToString())
If searchMatch() Then
Dim x As New System.Threading.Thread(AddressOf loadConfiguration)
Dim y As New System.Threading.Thread(AddressOf loadMatch)
Dim z As New System.Threading.Thread(AddressOf launchMinimap)
If My.Settings.configuration_auto_load Then
Try
x.Start()
Catch ex As Exception
End Try
End If
If My.Settings.loadMatch Then
Try
y.Start()
Catch ex As Exception
End Try
End If
If My.Settings.launchMinimap Then
Try
z.Start()
Catch ex As Exception
End Try
End If
x.Join()
y.Join()
z.Join()
tmr.Start()
End If
End Sub

How to properly exit from canceled BackgroundWorker async?

I'm currently using this code, (which is working) but I'm not satisfied with how it looks... Is there a more professional way to do it ?
Here's the code I use now :
Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
Try
If BackgroundWorker.CancellationPending Then
e.Cancel = True
Exit Sub
End If
LatestVersion = Web.DownloadString(UpdateLink)
If BackgroundWorker.CancellationPending Then
e.Cancel = True
Exit Sub
End If
If LatestVersion.Contains(InstalledVersion) Then
e.Result = UP_TO_DATE
Else
e.Result = OUTDATED
End If
Web.Dispose()
Catch ex As Exception
e.Result = ex.Message
End Try
End Sub
As you can see, I'm repeating two times the same condition. But imagine if there was more code, I should have repeat it more times...
My problem is that I would like to exit the Sub at anytime, as long as the BackgroundWorker.CancellationPending property is set to True.
I'm using the same condition two times because I wanna check if the operation has been canceled before, and after downloading of my string (I don't wanna wait for the string to be downloaded whereas I've already canceled the operation... it's a waste of time).
Should I use a While statement ? If yes, how ?
Don't use BackgroundWorker and this problem goes away. The code probably should be (mixture of VB and C#):
//Instance field:
CancellationTokenSource cts = ...;
//Update method
var downloadTask = HttpClient().DownloadString(UpdateLink, cts.Token);
await Task.WhenAll(downloadTask, cts.Token); //Wait with timeout
LatestVersion = await downloadTask;
If LatestVersion.Contains(InstalledVersion) Then
ShowResult(UP_TO_DATE);
Else
ShowResult(OUTDATED);
End If
And if you want to cancel, signal cts.
Also, error handling is missing. This is easy to test for and add.
I've found another solution. By the way, I've renamed Web by WebClient...
Here's my code :
Private Sub Form1_Load() Handles Form1.Load
AddHandler WebClient.DownloadStringCompleted, AddressOf WebClient_DownloadStringCompleted
End Sub
Private Sub BackgroundWorker_DoWork() Handles BackgroundWorker.DoWork
WebClient.DownloadStringAsync(New Uri(UpdateLink))
End Sub
Private Sub WebClient_DownloadStringCompleted(sender As Object, e As System.Net.DownloadStringCompletedEventArgs)
Dim Output As String = Nothing
If Not e.Cancelled Then
LatestVersion = e.Result
If LatestVersion.Contains(InstalledVersion) Then
Output = UP_TO_DATE
Else
Output = OUTDATED
End If
End If
End Sub
And, to cancel the operation, I run WebClient.CancelAsync().

VB Simple Threading using Delegates

I understand the concept of threading. I understand the concept of delegates but I am having trouble combining the two concepts. I followed a tutorial and I was able to make two counters start at the same time using multiple threads on my form. I was getting the cross threading error and I used the Me.CheckForIllegalCrossThreadCalls = False work around. I know my current method isnt ideal and I was wondering how I would use delegates to produce the same results. I have been at it all day and still cant seem to grasp the idea. How would I add delegates to the code below to allow two counters to work simultaneously on my form?
Public Class Form1
Dim i As Integer = 0
Dim i2 As Integer = 0
'declare two threads
'thread 1
Dim thread As System.Threading.Thread
'thread 2
Dim thread2 As System.Threading.Thread
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'replace countup() with, this will assign the countup method to thread 1
thread = New System.Threading.Thread(AddressOf countup)
thread.Start()
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
' countup2()
thread2 = New System.Threading.Thread(AddressOf countup2)
thread2.Start()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Label1.Text = i
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Label2.Text = i2
'We wont be able to see the label unless we refresh the form
Me.Refresh()
Loop
End Sub
End Class
I would love to see the code using delegates but what I would really like is to have the understanding of whats going on.
Thanks guys
Not sure if this is exactly what you're looking for, but here's my best shot at it:
Module Module1
Dim i As Integer = 0
Dim i2 As Integer = 0
Public Delegate Sub counting()
Sub Main()
Dim del2 As counting = AddressOf countup2
Dim callback2 As IAsyncResult = del2.BeginInvoke(Nothing, Nothing)
Dim del1 As counting = AddressOf countup
Dim callback1 As IAsyncResult = del1.BeginInvoke(Nothing, Nothing)
del2.EndInvoke(callback2)
del1.EndInvoke(callback1)
Console.ReadLine()
End Sub
Private Sub countup()
Do Until i = 100000
i = i + 1
Loop
Console.WriteLine("i = " & i)
End Sub
Private Sub countup2()
Do Until i2 = 100000
i2 = i2 + 1
Loop
Console.WriteLine("i2 = " & i2)
End Sub
End Module
Sorry I have the first and second parts reversed and it's a console app instead of a form, but I figured the important part was to demonstrate delegates...
As a note, I'm not sure how familiar you are with delegates, but I included the EndInvoke to make sure the program wouldn't terminate prior to the delegates finishing their operations. They are used to return any values or exceptions from the method call as well as making the program wait. (In this case, since it's a sub there is no return value, so I didn't bother worrying about it)
One should use Control.Invoke to execute a specified delegate on the thread that owns the control's underlying window handle. Also, replace Me.Refresh() with Thread.Sleep(1) to ensure that other threads get some execution time.
Private Sub countup()
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.Label1.Text = i.ToString())
Thread.Sleep(1)
Next
End Sub
Here's an example.
' n=0 n=1
Private threads As Thread() = New Thread(2 - 1) {Nothing, Nothing}
Private Sub ButtonsClick(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click
Dim n As Integer = -1
If (sender Is Me.Button1) Then
n = 0
ElseIf (sender Is Me.Button2) Then
n = 1
End If
If (n <> -1) Then
If (Me.threads(n) Is Nothing) Then
'Start new thread.
Me.threads(n) = New System.Threading.Thread(Sub() Me.CountUp(n))
Me.threads(n).Start()
Else
'Abort thread.
Me.threads(n).Abort()
Me.threads(n) = Nothing
End If
End If
End Sub
Public Sub DisplayCount(n As Integer, text As String)
'Inside UI thread.
If (n = 0) Then
Me.Label1.Text = text
ElseIf (n = 1) Then
Me.Label2.Text = text
End If
End Sub
Private Sub CountUp(n As Integer)
'Inside worker thread.
Try
If ((n < 0) OrElse (n > 1)) Then
Throw New IndexOutOfRangeException()
End If
For i As Integer = 0 To 100000
Me.Invoke(Sub() Me.DisplayCount(n, i.ToString()))
Thread.Sleep(1)
Next
Catch ex As ThreadAbortException
Me.Invoke(Sub() Me.DisplayCount(n, "Cancelled"))
Thread.Sleep(1)
Catch ex As Exception
'TODO: Handle other exceptions.
End Try
End Sub
using Me.CheckForIllegalCrossThreadCalls = False is not the right approach.
Basically, Cross-thread operation not valid exception is raised when a control is being updated from a thread other than the thread it was created on.
Each control exposes a InvokeRequired property that allows it to be updated in a thread-safe manner.
Therefore the right way to update the label is to use code like -
Private Delegate Sub UpdateLabelDelegate(i As Integer)
Private Sub UpdateLabel(i As Integer)
If Label1.InvokeRequired Then
Dim del As New UpdateLabelDelegate(AddressOf UpdateLbl)
Label1.Invoke(del, New Object() {i})
'Me.Refresh()
Else
' this is UI thread
End If
End Sub
Private Sub UpdateLbl(i As Integer)
Label1.Text = i.ToString()
End Sub
Delegate.BeginInvoke will execute the method on a thread pool thread. Once the method returns, the thread is returned to the pool.
So basically instead of starting a new thread, you will asynchronously execute the method using Delegate.BeginInvoke

DatagridView crashes App when updating causes scrollbars to appear

My vb.net 4.0 project is in-house trading platform.
A separate form called MsgPad contains a Datagridview called MsgPadDG.
The Form-load sub does two things: formats the datagridview and defines a timer for the gridview refresh.
A second Sub, UpdateMyMsg, receives updates from a threaded UIUpdaterEngine by means of NewMessage events, and updates the MyMessages Object (fundamentally a datatable).
A third sub, UpdateMdgPadDGW, updates/refreshes the datagridview using the invoke method (probably not necessary, because the timer is defined in the UI thread).
Finally The Datagridview properties: rows are defined as not user-editable, AutoSizeRowsMode = Displayedcells and ScrollBars = Both.
My problem is that the entire App crashes when the updating process adds rows beyond the limit of the datagridview, causing scrollbars to appear.
I tried without the timer (invoking datasource update directly in the UpdateMyMsg Sub), with the timer, but the problem is always there.
Weird thing: The Try-Catch block doesn't catch anything.
Thanks in advance for your replays.
Edoardo
HERE COMES THE CODE:
Public Class MsgPad
Private WithEvents _MyUIUpdaterEngine As MyUIUpdaterEngine = MyUIUpdaterEngine.Instance
Private MyMessages As New Messages
Private LastUpdate As DateTime = TimeValue("08:00:00")
Private ObjLock As New Object
Private NewMessages As Boolean = False
Dim UpdateDGWTimer As New System.Timers.Timer
Private Sub MsgPad_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Try
'--
MsgPadDG.DataSource = MyMessages.DTable
'--
With MsgPadDG
'--
.Columns("Msg_ID").Visible = False
.Columns("I_CommandID").Visible = False
.Columns("B_ID").Visible = False
.Columns("F_ID").Visible = False
.Columns("T_TV").Visible = False
.Columns("T_Sign").Visible = False
.Columns("T_Mde").Visible = False
.Columns("T_QU").Visible = False
.Columns("T_QI").Visible = False
.Columns("T_QF").Visible = False
.Columns("T_PI").Visible = False
.Columns("T_PF").Visible = False
'--
.Columns("Msg_RecDate").HeaderText = "Date"
.Columns("Msg_RecDate").Width = 50
.Columns("Msg_RecDate").DefaultCellStyle.Format = "HH:mm:ss"
.Columns("I_Symbol").HeaderText = "Symbol"
.Columns("I_Symbol").Width = 80
.Columns("I_Des").HeaderText = "Des"
.Columns("I_Des").Width = 180
.Columns("Msg_Text").HeaderText = "Message"
.Columns("Msg_Text").Width = 250
'--
End With
'--Crea timer per l'Update del DAtagridView
AddHandler UpdateDGWTimer.Elapsed, AddressOf UpdateMsgPadDGW
UpdateDGWTimer.Interval = 3500
UpdateDGWTimer.Enabled = True
.....
.....
End Sub
Private Sub UpdateMyMsg(ByVal CRec As OrderRecord, ByVal Msg As String) Handles _MyUIUpdaterEngine.NewMessage
Try
SyncLock ObjLock
'--Aggiorna la tabella MyMessages
MyMessages.Add(CRec, Msg)
NewMessages = True
'--Parla messaggio
Dim synth As New SpeechSynthesizer
Dim TalkMsg As String = Msg
If CRec.I_Des <> Nothing Then TalkMsg += " su: " + CRec.I_Des
synth.SpeakAsync(TalkMsg)
End SyncLock
.....
.....
End Sub
Private Sub UpdateMsgPadDGW(source As Object, e As ElapsedEventArgs)
'--Aggiorna MesssagePad
Try
SyncLock ObjLock
If NewMessages Then
MsgPadDG.ControlInvoke(Sub(l)
MsgPadDG.DataSource = MyMessages.DTable
MsgPadDG.Refresh()
MsgPadDG.ClearSelection()
End Sub)
NewMessages = False
End If
End SyncLock
.....
.....
End Sub
'--Aggiorna la tabella MyMessages
MyMessages.Add(CRec, Msg)
NewMessages = True
That's a fatal bug, the DGV is bound to MyMessages. So calling Add() in a worker thread causes the control to be updated from the wrong thread. You normally get an IllegalOperationException from accessing a control from the wrong thread but that unfortunately doesn't work for bound data.
You'll need to do this differently, have the worker add messages to a List instead. Then in the invoked code update MyMessages with these new messages.
Also note that your Timer is not a safe timer like you assumed in your question. Only a System.Windows.Forms.Timer is a safe one whose Tick event handler will run on the UI thread. You then also don't need to invoke anymore.