How to avoid Thread.Abort() in this case? - vb.net

I know that Thread.Abort() is not really safe, but I cannot imagine what it could cause in case below:
Private threadLoadList As Thread
Private Sub LoadList(ByVal argument As Integer)
Try
Refresh_Grid(GET_DATA(argument), argument) 'Fills grid with data loaded from database
Change_Cursor() 'Changes cursor to Cursor.Default
Catch ex As ThreadAbortException
'Do nothing
Catch ex As Exception
Error_log(ex.Message) ' Saves ex.Message in database
End Try
End Sub
Private Sub SomeValueChanged(sender As Object, e As EventArgs) Handles Control.ValueChanged
If Not threadLoadList Is Nothing Then
If threadLoadList.IsAlive Then
threadLoadList.Abort()
End If
End If
Cursor = Cursors.WaitCursor
threadLoadList = New Thread(AddressOf LoadList)
threadLoadList.Start(1)
End Sub
As you can see, user can change some value (ComboBox) and in result change content of the grid. Function GET_DATA() takes around 10 seconds, thus it is possible that user changes value of Combobox before grid is refreshed - that is why previously started thread is killed.
Is it dangerous? If yes, could you propose some other solution?
Ok, I understand ;). I try to avoid timeouts (in some cases query executes below 1 second) Is it better solution:
Private threadLoadList As Thread
Private Changed As Boolean = False
Private Sub LoadList(ByVal argument As Integer)
Try
Dim dt As DataTable = GET_DATA(argument)
'Enter Monitor
If Changed Then
Return
End IF
'Exit Monitor
Refresh_Grid(dt, argument) 'Fills grid with data loaded from database
Change_Cursor() 'Changes cursor to Cursor.Default
'Enter Monitor
Changed = False
'Exit Monitor
Catch ex As ThreadAbortException
'Do nothing
Catch ex As Exception
Error_log(ex.Message) ' Saves ex.Message in database
End Try
End Sub
Private Sub SomeValueChanged(sender As Object, e As EventArgs) Handles Control.ValueChanged
'Enter Monitor
Changed = True
'Exit Monitor
Cursor = Cursors.WaitCursor
Dim t As Thread = New Thread(AddressOf LoadList)
t.Start(1)
End Sub

First things first, unless there is extra code in Refresh_Grid that I am not seeing it looks like you are attempting to modify a UI element from a thread other than the UI thread. That is a big no-no. UI elements are designed to be accessed only from the UI thread so attempting to modify one on a worker thread will lead to unpredictable behavior.
In regards to your usage of Thread.Abort the answer is yes; it is dangerous. Well, it is dangerous in the sense that your application will throw an exception, corrupt data, crash, tear a hole in spacetime, etc. There are all kinds of failure modes that might be expected from aborting a thread. The best solution is to use a cooperative termination protocol instead of doing a hard abort. Refer to my answer here for a brief introduction on valid approaches.

Related

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 :)

I want to update my picture boxes in userform with really bigs tasks without the form freezing

I tried backgroundworker but doesnt work, as i need to update my form with each step and the backgroundworker.progresschanged only works as percentage. and if it were for only one step it could have been done with one. I also thought about making 10 different background workers and doing it that way. but then how would i cacth the exceptions and show the exceptions. and each of my try statements implements excel functions and excel workbook functions. which is also not possible in background worker.
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
pctrBoxStep1.Image = My.Resources.Yellow1
lblStep1.BackColor = Color.Yellow
workbook.Activate()
Try
'some stuff
Catch ex As Exception
int = -1
pctrBoxStep1.Image = My.Resources.red
lblStep1.BackColor = Color.Red
MessageBox.Show("There was an Error!!" & Environment.NewLine & ex.Message, "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error)
End Try
'Repeat for 9 more steps
End Sub
and each of my try statements implements excel functions and excel workbook functions. which is also not possible in background worker.
See this question. This is the only reason I can think of why you'd be having trouble using Excel functions in a BackgroundWorker.
I also thought about making 10 different background workers and doing it that way. but then how would i cacth the exceptions and show the exceptions.
From what you describe, this is probably the approach I would take. It sounds like you don't need to track the progress percentage of each worker at all. In that case, I would use the ReportProgress call only in your Catch statements. Something like:
Private Sub bgwStep1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwStep1.DoWork
Try
'some stuff
Catch ex As Exception
bgwStep1.ReportProgress(0)
System.Threading.Thread.Sleep(1000) 'Precautionary
End Try
End Sub
Private Sub bgwStep1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwStep1.ProgressChanged
int = -1
pctrBoxStep1.Image = My.Resources.red
lblStep1.BackColor = Color.Red
bgwStep1.CancelAsync()
End Sub
Private Sub bgw1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgw1.RunWorkerCompleted
bgw2.RunWorkerAsync()
End Sub
Notes:
Each BackgroundWorker must have its WorkerSupportsCancellation property set to True.
Sleep is called to make sure the UI thread has time to cancel the BackgroundWorker before it completes.
Keeping the data from the exception thrown is a little more complicated, but this question illustrates how to do so should you decide to go that route.

Cross Thread Error Trying To Open New Form Instance Inside Timer

I am trying to create little notification popups for my application and have created a new form that fades in and out and sits on top of my main form (seems to work okay).
My problem is that I have some code that sits inside a timer event that does some data checking every minute or so. Depending on the data results, I sometimes need to show a notification. However, it is causing me Cross-Thread errors (which is understandable), but I'm not sure how to get around it.
Example (in a nutshell) of what I am trying to do is:
Private Sub RefreshData(sender As Object, e As System.Timers.ElapsedEventArgs)
Try
MainRefreshTimer.Interval = GetInterval()
MainRefreshTimer.Start()
'Do some data checking here...
If data returns true then
Dim notify as New frmNewNotification("Some Text", 10) '<== Show some text for 10 seconds then close the form automatically
notify.Show() '<== Cross Thread Error occurs from this
End If
...
End Sub
I would try one of this ideas:
Shorcut: Put this in your Form_Load
Control.CheckForIllegalCrossThreadCalls = False
Or, better, something like:
Private Sub delRefreshData(data as Object)
If Me.InvokeRequired Then
' Invoke(New MethodInvoker(AddressOf delRefreshData)) ' no params
Invoke(New MethodInvoker(Sub() delRefreshData(data)))
Else
'Do some data checking here...
If data returns true then
Dim notify as New frmNewNotification("Some Text", 10)
notify.Show() '
End If
End if
Using InvokeRequired vs control.InvokeRequired
Edited:
To avoid in future be blamed for that Shorcut, I have to say that
Control.CheckForIllegalCrossThreadCalls isn't a good advice/solution, as is discussed here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on

Session Implementation in windows Forms

I am using a Windows form application due to added security measures i have to work around for session in my application.Currently i am using a Timer to achieve the functionality I am able to close the form but i need to again restart the application to return to the login form.I am using the below code
Private Sub sessionTimer_Tick(sender As Object, e As EventArgs) Handles sessionTimer.Tick
Try
Me.sessionTimer.Stop()
Me.sessionTimer.Enabled = False
Process.Start(Application.StartupPath + "\application.exe")
Process.GetCurrentProcess().Kill()
Catch ex As Exception
End Try
End Sub
I am getting an exception when i use the above method and it doesn't serve the purpose,also i have already tried using Application.Restart didn't work out.Please help i am new to windows form. Also adding to this in order to reset the timer i am using the below code.
Private Sub frmMain_MouseMove(sender As Object, e As MouseEventArgs) Handles MyBase.MouseMove
Me.sessionTimer.Stop()
Me.sessionTimer.Start()
End Sub
But this doesn't seem to work the main form has menu's which i am using to navigate to other forms so the idle time should not include the time spent in other forms which are opened via the menu's. What event should i use in frmMain to handle this problem.Thanks
Just let the Framework do the work.
Application.Restart()
If your session Timer fires from a background thread (maybe you use System.Timers.Timer instead of System.Windows.Forms.Timer) you propable have to sync with your main thread.
Me.Invoke(new MethodInvoker(Addressof Application.Restart))
If Application.Restart does not work there is propably something wrong with your app. You should try the following.
If you created threads, be sure they are created with "thread.IsBackground = true" otherwise they will keep your process open
Stop your timers and background workers.
Be sure you don't have forms that handle the FormClosing event and set e.Cancel = true. In that case you have to take e.CloseReason into account.
User Mark Hurd posted a great post about what happens during Application.Restart, have a look here
Iam using this code to restart my application. It works very well.
System.Diagnostics.Process.Start(Application.ExecutablePath) 'First start a new instance
Me.Close() 'Close the current application
If this doesnt work. I think there is no way around than using another application which restarts the Process. Here is a code example (second application)
Private Shared Sub RestartApp(pid As Integer, applicationName As String, arguments As String)
' Wait for the process to terminate
Dim process__1 As Process = Nothing
Try
process__1 = Process.GetProcessById(pid)
process__1.WaitForExit(1000)
' ArgumentException to indicate that the
' process doesn't exist?
Catch ex As ArgumentException
End Try
Process.Start(applicationName, arguments)
'Arguments?
End Sub
Source of the code

Stop all threads if an error is detected in one of them

I am trying to implement multi-threading into an app. The app needs to create a variable number of threads whilst passing variables across. I can easily create the threads, however I am trying to figure out a way to be able to stop all threads at once and if an error is caught in any one of these threads, stop all of them.
My current solution is to enclose the functions in a loop that checks if a boolean value is "True", in which case the thread carries on. If there is an error, I change the value to "False" and all the threads stop. Similarly if I want to stop the threads manually I can set the value to "false" from a function.
Is there a better solution to this, as the main issue is the threads must reach the end of the loop before they stop completely?
Try this
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim foo As New List(Of Threading.Thread)
Threading.Interlocked.Exchange(stopRun, 0L)
For x As Integer = 1 To 5 'start five threads
Dim t As New Threading.Thread(AddressOf workerThrd)
t.IsBackground = True
t.Start()
foo.Add(t) 'add to list
Next
Threading.Thread.Sleep(2000) 'wait two seconds
Threading.Interlocked.Increment(stopRun) 'signal stop
For Each t As Threading.Thread In foo 'wait for each thread to stop
t.Join()
Next
Debug.WriteLine("fini")
End Sub
Dim stopRun As Long = 0L
Private Sub workerThrd()
Do While Threading.Interlocked.Read(stopRun) = 0L
Threading.Thread.Sleep(10) 'simulate work
Loop
Debug.WriteLine("end")
End Sub
Running the threads in a while True block should be fine. Once its false, you could just iterate over the threads and call thread.abort() even though sometimes using abort isnt a good idea. Using a list of threads could be helpful. I dont know how you are creating your threads but this should be easy to understand.
Dim listThreads As List(Of Threading.Thread)
'create/instantiate your threads adding them to the collection something like the following
For i = 1 To numberofthreadsyouneed
Dim tempThread As Threading.Thread = New Threading.Thread
tempThread.Start()
tempThread.Add(tempThread)
next
Instead of using a while block just do a Try catch. inside the catch iterate over the list to abort the threads
Catch ex As Exception
For each Thread in listThreads
Thread.Abort()
Next
end Try
If you want more control
Here
is a pretty sweet thing called Tasks that they come out with a while back. It gives you a little more control over your threads
Hope this helps.