VB.NET : Thread is running or terminated; it cannot restart - vb.net

I've encounter this error when using multi-threading. I'm new with it, in my windows application this code works. But I transfer it to windows services I've received "Thread is running or terminated; it cannot restart." I'm using System.Timers.Timer instead of System.Windows.Forms.Timer as recommended when creating it in Windows services. This windows services will export some XML file from database, so I need a timer. So time to time, it will check if there's a new products or customer in the database which reads the function below. By default, I've hard coded the time to 1min for testing. Also, I've created a boolean variable if the function is not finish yet. It will not override.
Here's my code :
Dim oIsproc_BP As Boolean
Dim oIsproc_ItemMaster1 As Boolean
Dim thrd As Thread
Protected Overrides Sub onstart(ByVal args() As String)
tmr.Interval = 1000
AddHandler tmr.Elapsed, AddressOf tmr_Elapsed
tmr.Start()
End Sub
Private Sub tmr_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles tmr.Elapsed
oIsproc_BP = False
oIsproc_ItemMaster1 = False
tSecItemMaster.Interval = 60000'oInterval(0)
AddHandler tSecItemMaster.Elapsed, AddressOf tSecItemMaster_Elapsed
tSecItemMaster.Start()
tSecCustomer.Interval = 60000'oInterval(2)
AddHandler tSecCustomer.Elapsed, AddressOf tSecCustomer_Elapsed
tSecCustomer.Start()
tmr.Stop()
End Sub
Private Sub tSecItemMaster_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles tSecItemMaster.Elapsed
If Not oIsproc_ItemMaster1 Then
oIsproc_ItemMaster1 = True
thrd = New Thread(DirectCast(Function() oItemMaster(), ThreadStart))
thrd.Start()
End If
Return
End Sub
Private Sub tSecCustomer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles tSecCustomer.Elapsed
If Not oIsproc_BP Then
oIsproc_BP = True
thrd = New Thread(DirectCast(Sub() oBPartners(, "C"), ThreadStart))
thrd.Start()
End If
Return
End Sub
And for my function :
Private Function oItemMaster(Optional ByVal FirstLoad As Boolean = False, Optional oType As Integer = 1)
''My code here
oIsproc_ItemMaster1 = False
End Function
Private Sub oBPartners(Optional ByVal FirstLoad As Boolean = False, Optional CardType As String = "C")
''My code here
oIsproc_BP = False
End Function

You have a race - you're using a single variable (thrd) to hold one of two instances of a Thread that you might create. Consider both timers firing at the same time, and the threads that service the timers being interleaved as follows:
Timer 1 (tSecItemMaster_Elapsed) Timer 2 (tSecCustomer_Elapsed)
If Not oIsproc_ItemMaster1 Then
oIsproc_ItemMaster1 = True
If Not oIsproc_BP Then
oIsproc_BP = True
thrd = New Thread(...)
thrd = New Thread(...)
thrd.Start()
End If
Return
thrd.Start()
End If
Return
And that's why you get the error message - both timers are trying to start the single Thread object that was created inside tSecItemMaster_Elapsed, and the Thread created inside of tSecCustomer_Elapsed is never started. Other inter-leavings will introduce similar issues.
A quick fix would be to create a separate field for storing each thread. I think you may still have a couple of race conditions but they're not leaping out at me at the moment.

Related

Cannot update textbox vb.net -crossthreading

I am trying to make an application to run multiple (adb specially) commands and get the output and display in a label.
First of all, i need to start the process to execute the commands. Thanks to stackoverflow and #pasty I found this (second reply): How to get Output of a Command Prompt Window line by line in Visual Basic?
Well, i thought that because it outputted to the console, it would be simple to just write it to the label. BIG MISTAKE! It gives me a cross threading error!
A little bit of googling and stack overflow I found this: vb.net accessed from a thread other than the thread it was created on
Well, i tried to implement that, but the program just crashes freezes.
Here is the code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' store error output lines
Dim executable As String() = {"adb", "adb"}
Dim arguments As String() = {"help", "reboot"}
For i As Integer = 0 To 0
Dim process = New Process()
process.StartInfo = createStartInfo(executable(i), arguments(i))
process.EnableRaisingEvents = True
AddHandler process.Exited, Sub(ByVal sendera As Object, ByVal ea As System.EventArgs)
Console.WriteLine(process.ExitTime)
Console.WriteLine(". Processing done.")
'UpdateTextBox(ea)
End Sub
' catch standard output
AddHandler process.OutputDataReceived, Sub(ByVal senderb As Object, ByVal eb As DataReceivedEventArgs)
If (Not String.IsNullOrEmpty(eb.Data)) Then
Console.WriteLine(String.Format("{0}> {1}", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"), eb.Data))
'UpdateTextBox(eb.Data)
End If
End Sub
' catch errors
AddHandler process.ErrorDataReceived, Sub(ByVal senderc As Object, ByVal ec As DataReceivedEventArgs)
Console.WriteLine(String.Format("! {0}", ec.Data))
Dim a As String = String.Format("! {0}", ec.Data)
'UpdateTextBox(a)
End Sub
' start process
Dim result = process.Start()
' and wait for output
process.BeginOutputReadLine()
' and wait for errors :-)
process.BeginErrorReadLine()
process.WaitForExit()
Next
End Sub
Private Sub UpdateTextBox(ByVal a As String)
If Me.InvokeRequired Then
Dim args() As String = {a}
Me.Invoke(New Action(Of String)(AddressOf UpdateTextBox), args)
Return
End If
Label1.Text += "a"
End Sub
Private Function createStartInfo(ByVal executable As String, ByVal arguments As String) As ProcessStartInfo
Dim processStartInfo = New ProcessStartInfo(executable, arguments)
processStartInfo.WorkingDirectory = Path.GetDirectoryName(executable)
' we want to read standard output
processStartInfo.RedirectStandardOutput = True
' we want to read the standard error
processStartInfo.RedirectStandardError = True
processStartInfo.UseShellExecute = False
processStartInfo.ErrorDialog = False
processStartInfo.CreateNoWindow = True
Return processStartInfo
End Function
And the source code: https://github.com/iAmAOpenSource/SyncfusionWindowsFormsApplication3
The call to process.WaitForExit() will block the UI thread until the spawned process exits, but while processing the output in the process.OutputDataReceived event you are calling Me.Invoke which tries to run the code on the UI thread, which is blocked, so the program freezes. You could move the logic in Button1_Click onto another thread, e.g.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Task.Run(
Sub()
... current logic
End Sub
)
End Sub
That way the UI thread won't be blocked and the Me.Invoke call won't cause a deadlock.

Multithreading doesn't work

I'm making a simple multithreading program to explain the working of threading. I want two counters counting on the same time but it doesn't work.
It only works if I use: CheckForIllegalCrossThreadCalls = False. But, I want to program in a proper way.
Code:
Dim Thread1 As System.Threading.Thread
Dim Thread2 As System.Threading.Thread
Private Delegate Sub SetTeller1()
Private Sub teller1()
If teller1Label.InvokeRequired Then
Invoke(New SetTeller1(AddressOf teller1))
Else
For i As Integer = 0 To 1000
teller1Label.Text = i
Refresh()
Next
End If
End Sub
Delegate Sub SetTeller2()
Private Sub teller2()
If teller2Label.InvokeRequired Then
Invoke(New SetTeller2(AddressOf teller2))
Else
For i As Integer = 0 To 1000
teller2Label.Text = i
Refresh()
Next
End If
End Sub
Private Sub teller1Button_Click(sender As Object, e As EventArgs) Handles teller1Button.Click
Thread1 = New Threading.Thread(AddressOf teller1)
Thread1.Start()
End Sub
Private Sub teller2Button_Click(sender As Object, e As EventArgs) Handles teller2Button.Click
Thread2 = New Threading.Thread(AddressOf teller2)
Thread2.Start()
End Sub
The multithreading works perfectly, but you are not utilizing it. The only thing you're currently doing in the background thread is calling Invoke, which means that your thread will exit within a few milliseconds and then be discarded.
Once you call Invoke the execution of the teller1 or teller2 method is moved to the UI thread, meaning it will block the UI until its execution is finished. You should only invoke when you are to update the UI, and perform all iterations in the background thread.
Here's an example of how you can do it more properly:
Delegate Sub SetTeller1(ByVal Text As String)
Private Sub teller1()
For i As Integer = 0 To 1000
SetTeller1Text(i)
Next
End Sub
Private Sub SetTeller1Text(ByVal Text As String)
If Me.InvokeRequired Then
Me.Invoke(New SetTeller1(AddressOf SetTeller1Text), Text)
Else
teller1Label.Text = Text
Me.Refresh()
End If
End Sub
For improved readability I changed for example Invoke(...) to Me.Invoke(...).
Also I'm not sure why you're calling Refresh() as it isn't necessary and will just cause extra redrawing of the entire container (guessing this is a form).

Showing WinSCP .NET assembly transfer progress on WinForm's progress bar

Have some main form on which I am calling file downloading from FTP. When this operation is raised i want to see new form as ShowDialog and progress bar on it to be shown meantime, then show the progress and close new form and back to main form. My code is working however, when it will process is started my main form freezes and after while new form is appearing and then closing. What I would like to correct is to show this new form to be showed straightaway after process is executed. Can you take a look and tell me whats wrong?
This is out of my main form the download process called:
Dim pro As New FrmProgressBarWinscp(WinScp, myremotePicturePath, ladujZdjeciaPath, True)
FrmProgressBarWinscp is as follows:
Public Class FrmProgressBarWinscp
Property _winScp As WinScpOperation
Property _remotePicture As String
Property _ladujZdjecia As String
Property _removesource As String
Public Sub New()
InitializeComponent()
End Sub
Sub New(winscp As WinScpOperation, remotePicture As String, ladujzdjecia As String, removesource As Boolean)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_winScp = winscp
_remotePicture = remotePicture
_ladujZdjecia = ladujzdjecia
_removesource = removesource
ShowDialog()
End Sub
Sub Run()
Try
Cursor = Cursors.WaitCursor
_winScp.GetFile(_remotePicture, _ladujZdjecia, _removesource)
ProgressBar1.Minimum = 0
ProgressBar1.Maximum = 1
ProgressBar1.Value = 0
Do
ProgressBar1.Value = WinScpOperation._lastProgress
ProgressBar1.Refresh()
Loop Until ProgressBar1.Value = 1
Cursor = Cursors.Default
'Close()
Catch ex As Exception
Finally
If _winScp IsNot Nothing Then
_winScp.SessionDispose()
End If
System.Threading.Thread.Sleep(10000)
Close()
End Try
End Sub
Private Sub FrmProgressBarWinscp_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Run()
End Sub
End Class
Winscp my own class and used methods:
...
Function GetFile(source As String, destination As String, Optional removeSource As Boolean = False)
Dim result As Boolean = True
Try
session.GetFiles(source, destination, removeSource).Check()
Catch ex As Exception
result = False
End Try
Return result
End Function
Private Shared Sub SessionFileTransferProgress(sender As Object, e As FileTransferProgressEventArgs)
'Print transfer progress
_lastProgress = e.FileProgress
End Sub
Public Shared _lastProgress As Integer
...
Further discussion nr 3:
Main form:
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim forma As New FrmProgressBar
forma.ShowDialog()
Progress bar form:
Public Class FrmProgressBar
Public Sub New()
InitializeComponent()
End Sub
Sub Run()
Try
Do
ProgressBar1.Value = WinScpOperation._lastProgress
ProgressBar1.Refresh()
Loop Until ProgressBar1.Value = 1
Cursor = Cursors.Default
Catch ex As Exception
Finally
MsgBox("before sleep")
System.Threading.Thread.Sleep(10000)
MsgBox("after sleep sleep")
Close()
End Try
End Sub
Private Sub FrmProgressBarWinscp_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Run()
End Sub
End Class
Point nr. 4:
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim pic As New Waiting
pic.ShowDialog()
Task.WaitAll(tsk)
pic.Close()
Point 5:
Dim pic As New Waiting
pic.ShowDialog()
Dim tsk As Task = Task.Factory.StartNew(Sub() WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, pic, True))
Task.WaitAll(tsk)
'pic.Close()
In some other class (maybe didn't mentioned before this method is placed in diffrent class - my custom one)
Public Function GetFile(source As String, destination As String, formclose As InvokeCloseForm, Optional removeSource As Boolean = False) As Boolean
Dim result As Boolean = True
Try
session.GetFiles(source, destination, removeSource).Check()
Catch ex As Exception
result = False
End Try
formclose.RUn()
Return result
End Function
Interface:
Public Interface InvokeCloseForm
Sub RUn()
End Interface
Waiting form :
Public Class Waiting
Implements InvokeCloseForm
Public Sub RUn() Implements InvokeCloseForm.RUn
Me.Close()
End Sub
End Class
The Session.GetFiles method in blocking.
It means it returns only after the transfer finishes.
The solution is to:
Run the WinSCP transfer (the Session.GetFiles) in a separate thread, not to block the GUI thread.
For that see WinForm Application UI Hangs during Long-Running Operation
Handle the Session.FileTransferProgress event.
Though note that the event handler will be called on the background thread, so you cannot update the progress bar directly from the handler. You have to use the Control.Invoke to make sure the progress bar is updated on the GUI thread.
For that see How do I update the GUI from another thread?
A trivial implementation is below.
For a more version of the code, see WinSCP article Displaying FTP/SFTP transfer progress on WinForms ProgressBar.
Public Class ProgressDialog1
Private Sub ProgressDialog1_Load(
sender As Object, e As EventArgs) Handles MyBase.Load
' Run download on a separate thread
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf Download))
End Sub
Private Sub Download(stateInfo As Object)
' Setup session options
Dim mySessionOptions As New SessionOptions
With mySessionOptions
...
End With
Using mySession As Session = New Session
AddHandler mySession.FileTransferProgress,
AddressOf SessionFileTransferProgress
' Connect
mySession.Open(mySessionOptions)
mySession.GetFiles(<Source>, <Destination>).Check()
End Using
' Close form (invoked on GUI thread)
Invoke(New Action(Sub() Close()))
End Sub
Private Sub SessionFileTransferProgress(
sender As Object, e As FileTransferProgressEventArgs)
' Update progress bar (on GUI thread)
ProgressBar1.Invoke(
New Action(Of Double)(AddressOf UpdateProgress), e.OverallProgress)
End Sub
Private Sub UpdateProgress(progress As Double)
ProgressBar1.Value = progress * 100
End Sub
End Class
You may want to disable the progress form (or its parts) during the operation, if you want to prevent the user from doing some operations.
Use the .Enabled property of the form or control(s).
Easier, but hacky and generally not recommendable solution, is to call the Application.DoEvents method from your existing SessionFileTransferProgress handler.
And of course, you have to update the progress bar from the the SessionFileTransferProgress as well.
Private Shared Sub SessionFileTransferProgress(
sender As Object, e As FileTransferProgressEventArgs)
'Print transfer progress
ProgressBar1.Value = e.FileProgress
Application.DoEvents
End Sub
And the progress bar's .Minimum and .Maximum must be set before the Session.GetFiles.
But do not do that! That's a wrong approach.
And still, you need to disable the forms/controls the same way as in the correct solution above.

Activating timer from another thread

So the purpose of it is to check the connection of the ftp server and if the ftp is up then enable timer1. I've read that threads don't work as synchonized and that is causing the problem. Without the thread it works fine, but the program hangs and stops responding constantly.
How can i activate a timer from another thread?
Maybe invoking and delegating would work? But i don't know how to do that.
Public Function CanPortOpen(ByVal HostName As String, ByVal Port As Integer) As Boolean
Dim TCP As New System.Net.Sockets.TcpClient
Try
TCP.Connect(HostName, Port)
Catch
End Try
If TCP.Connected = True Then
CanPortOpen = True
TCP.Close()
Timer1.Enabled = True
Else
CanPortOpen = False
TCP.Close()
Timer1.Enabled = False
FTPup.Abort()
End If
End Function
Public Sub CheckConnection()
CanPortOpen("HostName", Port)
End Sub
Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
TestFTP = New System.Threading.Thread(AddressOf CheckConnection)
TestFTP.IsBackground = True
TestFTP.Priority = Threading.ThreadPriority.AboveNormal
TestFTP.Start()
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
FTPup = New System.Threading.Thread(AddressOf UploadToFTP)
FTPup.IsBackground = True
FTPup.Priority = Threading.ThreadPriority.AboveNormal
FTPup.Start()
End Sub
I think before you start getting in too deep with threads you should start by looking at the BackgroundWorker component. You can find it on your toolbar in the designer and can drop it on your form. It gives you several events
DoWork - hook up this event with whatever you want done in a background thread
RunWorkerCompleted - hook up this event to run code in the main (UI) thread, triggered when the thread completes. Code here can interact with UI objects as normal.
There are other events that allow you to report progress to your main thread, etc. The purpose of the BackgroundWorker component is to make simple multithreading tasks like this easier.
Documentation is -> here
See -> here for examples of how to pass data from the worker thread to the main thread using EventArgs.
Alternatively, if you just want to run the timer from your thread you can do it like this :
'Declare an appropriate delegate
Delegate Sub dlgTimerEnable(ByVal enable as Boolean)
'the delegate should match the method signature
Private Sub TimerEnable(ByVal enable as Boolean)
Timer1.Enabled = enable
End Sub
and then in your thread procedure
Public Function CanPortOpen(ByVal HostName As String, ByVal Port As Integer) As Boolean
Dim TCP As New System.Net.Sockets.TcpClient
Try
TCP.Connect(HostName, Port)
Catch
End Try
If TCP.Connected = True Then
CanPortOpen = True
TCP.Close()
Me.Invoke(New dlgTimerEnable(AddressOf TimerEnable), New Object() {True})
Else
CanPortOpen = False
TCP.Close()
Me.Invoke(New dlgTimerEnable(AddressOf TimerEnable), New Object() {False})
FTPup.Abort()
End If
End Function
Here Invoke causes the method to be executed on the thread that owns Timer1 (assuming this is a method in your form where Me would refer to your form). The arguments are passed as an object.
You can even do this as a general way to work with any timer, for example :
Delegate Sub dlgTimerEnable(ByRef tmr As Timer, ByVal enable As Boolean)
Private Sub TimerEnable(ByRef tmr As Timer, ByVal enable As Boolean)
tmr.Enabled = enable
End Sub
and then :
Me.Invoke(New dlgTimerEnable(AddressOf TimerEnable), New Object() {Timer1, True})
This makes your delegate general - you can pass it any timer and enable/disable it.

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.