Properly closing a form running a loop in a new thread - vb.net

I have method in my form that is running a do while loop in a new thread.
That for loop exits once a sentinel value is set to true (set by another method that handles Me.FormClosing)
The issue is that when the form closes, I get occasionally get two exceptions.
ObjectDisposedException and ComponentModel.Win32Exception.
How do I properly exit a form without "eating" these exceptions and ignoring them.
Code:
Dim _exit As Boolean
Public Sub test()
Dim task As Thread = New Thread(
Sub()
Do
checkInvoke(Sub() a.append("a"))
Loop While _exit = False
End Sub)
End Sub
Private Sub checkInvoke(ByVal _call As Action)
If Me.InvokeRequired Then
Me.Invoke(Sub() checkInvoke(_call))
Else
_call.Invoke()
End If
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
_exit = True
End Sub

Where does the error come from ?
This can be a bit confusing but it actually is pretty logical...
The user (or something else) closes the form.
FormClosing is then called which sets _exit to True
Then the Form closes itself, destroying its handle.
Now, it depends where it sometimes throws an exception :
Either the Thread just finished the Invoke or the Loop, check the _exit value then ends the loop, everything goes fine.
Either it just began the Invoke, then it calls a method invoking the UI thread to modify something on the form that has just been disposed, no more Handle to this form, leading to ObjectDisposedException
How to prevent this ?
One thing you can do is, in your FormClosing event, wait for the Thread to end, then letting the system close the form :
Private _isFinished As Boolean = False
Private _exit As Boolean = False
Public Sub test()
Dim task As Thread = New Thread(
Sub()
Do
checkInvoke(Sub() a.append("a"))
Loop While _exit = False
'We inform the UI thread we are done
_isFinished = True
End Sub)
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
_exit = True
While Not _isFinished
Application.DoEvent() 'We can't block the UI thread as it will be invoked by our second thread...
End While
End Sub

I am not familiar to VB, but I have done a similar thing in c++. The main problem is that the for loop does not finish yet when the form is closing. You can just hide the form, wait for the thread to finish and then close the form. You can use flags tomark the stopping of the paralel thread.

Related

Threading and modal form windows

VB.Net code.
I have a program where I am running a process in a thread and in that thread I need to have a pop up message information box that is non-modal. The main process is in a thread because it has to run in parallel and the user can initiate this process many times at the same time.
I read that the modal message box needs to be a custom form that is also ran from a thread to not block the program from continuing on. such as .Show() stops the program and waits for the user input. And you have to use .ShowDialog() via a thread
My code:
Calling initial thread:
Public Event Report As EventHandler
'In a method
Task.Run(Function() BackgroundThread())
Private Function BackgroundThread() As Task()
RaiseEvent Report(Me, New System.EventArgs)
End Function
In the Report method I have a snippet of code that then calls the form window to pop up the modal window:
Private mDiaplayMessageBox As NonModalPopUp
Private Sub DisplayMessageBox()
mDiaplayMessageBox = New NonModalPopUp()
Task.Run(Sub() mDiaplayMessageBox.ShowDialog())
End Sub
The issue I am having is that when I am finished with the report method I want to close this popup message. But when there is more than one of these pop up windows open at a time, only the last window opened will close and the program loses the handle I think to the other pop up windows and they will not close.
To close the windows I have in the modal form this code
Public Sub CloseMe()
'This will grab the thread that this window is running on, solves Cross-Threading issue.
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf CloseMe))
Exit Sub
End If
Me.BackColor = Color.Red
Me.Close()
End Sub
This first time this code is called its will hit the Me.Invoke and then close the window. However, on any subsequent calls when it gets to Me.InvokeRequired this will then be set to false, not called the Me.Invoke and go to the Me.Close() but it will not close the window.
I tried to do something where I grab the Handle intptr value but when ever I vent just look at that value the program immediately throws a cross-threading exception.
All I want to do is close the other windows which does not seem like a hard task but I do not know what I am missing.
One of approaches you can follow to achieve your goal might be as code below shows:
You can create a custom event which you can use as a “call” to listen to for the closure of your form.
Public Class Form1
Dim frm2 As Form2
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
frm2 = New Form2
Task.Run(Sub()
AddHandler CloseFrm2, Sub()
Dim CloseMe As Action = Sub()
frm2.Close()
frm2.Dispose()
End Sub
If frm2.InvokeRequired Then
frm2.Invoke(Sub() CloseMe())
Else
CloseMe()
End If
End Sub
frm2.ShowDialog()
End Sub)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
RaiseEventCloseFrm2()
End Sub
End Class
Module EventHelper
Public Event CloseFrm2()
Sub RaiseEventCloseFrm2()
RaiseEvent CloseFrm2()
End Sub
End Module

WinForms.IllegalCrossThreadCall with filewatcher

I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.

Accessing UI thread controls from 2 joining multi thread

I'm currently working on a small auto-update project for my company. After some research on multi-threading, I manage to built up the code below :
Thread #01 :
Private Sub startUpdate()
If InvokeRequired Then
Invoke(New FTPDelegate(AddressOf startUpdate))
Else
'some code here
End If
End Sub
Thread #02 which is joined by thread #01 :
Private Sub startProcess()
myThread = New Thread(Sub() startUpdate())
myThread.Start()
myThread.Join()
'another code goes here
Me.close
End Sub
And thread #02 is accessed when the form loads :
Private Sub SUpdater_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
myThread1 = New Thread(Sub() startProcess())
myThread1.Start()
End Sub
There are 2 things which I'm stuck with :
I can't access Me.close from thread #01. It fires an error:
Control is in another thread
The main form froze even though I called another thread.
Please help me fix this error.
Thank you very much.
Invocation is required every time you are to access UI elements. Calling Me.Close() starts to dispose all the form's elements (components, buttons, labels, textboxes, etc.), causing interaction with both the form itself, but also everything in it.
The only things you are not required to invoke for are properties that you know doesn't modify anything on the UI when get or set, and also fields (aka variables).
This, for example, would not need to be invoked:
Dim x As Integer = 3
Private Sub Thread1()
x += 8
End Sub
To fix your problem you just need to invoke the closing of the form. This can be done simply using a delegate.
Delegate Sub CloseDelegate()
Private Sub Thread1()
If Me.InvokeRequired = True Then 'Always check this property, if invocation is not required there's no meaning doing so.
Me.Invoke(New CloseDelegate(AddressOf Me.Close))
Else
Me.Close() 'If invocation is not required.
End If
End Sub

Timer which can be called from a class and form both

I have a simple WinForm application. The main entry point of the application is mainForm. I am using a Timer on the form and the timer interval is being set to 2000ms. The Tick event of the Timer is as below,
Public myValue as Integer = 100
Private Sub myTimer_Tick(sender As Object, e As EventArgs) Handles myTimer.Tick
If myValue = 0 Then
myTimer.Enabled = False
Else
myValue = myValue -1
End If
End Sub
The timer is being called at the start of the application when mainForm is loaded. Now myValue is a global variable and here for the purpose of simplicity I have used this otherwise it is replaced by some process count mechanism which is not required to be explained here.
I am able to use this approach as long as I am using Windows.Forms.Timer placed on some specific Form. I have two more scenarios in which this approach fails.
1 - I have to use the same functionality on some other form and for this currently I am using a separate Timer on another Form and it has its own Tick event.
2 - I have to use the same functionality from another module/class and I am unable to achieve this because for this to work I require a Form.
Now for a start I have looked into Threading.Timer. The problem I am facing is that I don't know how to wait for Threading.Timer to finish as the control goes to next line after Threading.Timer is called. I am not sure whether this can be done with the help of WaitHandle or not. Also I have read that Threading.Timer creates a separate Thread for each of its Tick. This seems like an overkill in my simple scenario.
I just want to use the Timer functionality without the need of Form. Also I could create the similar functionality using a Do Loop with Thread.Sleep inside it but unless I am sure that my Timer functionality is not going to work in other situations I am going to stick to my Timer approach.
I see ... If thats the case, you should really create a second thread that runs a loop. That thread has some exiting parameters that indicates that operation is completed and the Thread itself is set to Isbackground = false.
However, you could also do this ...
Imports System.Timers
Public Class Main
Private Shared WithEvents m_oTimer As Timers.Timer = Nothing
Private Shared m_oWaitHandle_TimerHasCompleted As System.Threading.AutoResetEvent = Nothing
Public Shared Sub Main()
Try
'Application Entry point ...
'Create the global timer
m_oTimer = New Timers.Timer
With m_oTimer
.AutoReset = True
.Interval = 2000
.Start()
End With
'Create the WaitHandle
m_oWaitHandle_TimerHasCompleted = New System.Threading.AutoResetEvent(False)
'Show your form
Dim oFrm As New Form1
Application.Run(oFrm)
'Wait for the timer to also indicate that it has finished before exiting
m_oWaitHandle_TimerHasCompleted.WaitOne()
Catch ex As Exception
'Error Handling here ...
End Try
End Sub
Private Shared Sub m_oTimer_Elapsed(sender As Object, e As ElapsedEventArgs) Handles m_oTimer.Elapsed
'Timer will fire here ...
Try
If 1 = 2 Then
m_oWaitHandle_TimerHasCompleted.Set()
End If
Catch ex As Exception
'Error Handling ...
End Try
End Sub
End Class
Please note that 'm_oWaitHandle_TimerHasCompleted.Set()' will never run, you'll have to add a condition ... however, once run, the WaitOne will complete and the application will exit as required.
Hows zat?
Sounds to me like you want to create a single instance of a timer, that does not need to be instantiated via a form?
If so ... Create a new class called 'Main' and copy the following into it.
Imports System.Timers
Public Class Main
Private Shared WithEvents m_oTimer As Timers.Timer = Nothing
Public Shared Sub Main()
Try
'Application Entry point ...
'Create the global timer
m_oTimer = New Timers.Timer
With m_oTimer
.AutoReset = True
.Interval = 2000
.Start()
End With
'Show your form
Dim oFrm As New Form1
Application.Run(oFrm)
Catch ex As Exception
'Error Handling here ...
End Try
End Sub
Private Shared Sub m_oTimer_Elapsed(sender As Object, e As ElapsedEventArgs) Handles m_oTimer.Elapsed
'Timer will fire here ...
Try
Catch ex As Exception
'Error Handling ...
End Try
End Sub
End Class
Once done, right click on your project and select 'Properties'. In the Application tab you'll see a checkbox called 'Enable Application framework'. Uncheck this box. Now, in the dropdown called 'Startup Object' you should now see 'Sub Main' .... Select that.
When the application runs, Sub Main will now run instead of your form.
This will create the Timer that will fire outside of your form. Please note, as you're not syncing it, I believe it'll run inside a thread so be a little careful there :)

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.