WinForms.IllegalCrossThreadCall with filewatcher - vb.net

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.

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

Detecting when a BackgroundWorker has its job done? Depending on circumstances, it needs to be rerun

My class watches a directory for incoming files. It does do so with a FileSystemWatcher object, only monitoring the FSW's Created events.
On a Created event, I start a potentially time-consuming process (file-deserialization is needed, sending an event to the client using my class, in which all sorts of things might happen). Thus, I start a BackgroundWorker object to do all this work, ultimately culminating in the received file's removal.
However, during all this work, new files may appear. In the Created event I check, if the BGW is still busy, and if so, I just store the fully qualified name in a queue for later consumption.
Public Sub New(Path As String)
FSM = New FileSystemWatcher
With FSW
.Path = Path
AddHandler .Created, AddressOf pFileArrived
End With
BGW = New BackgroundWorker
With BGW
.WorkerReportsProgress = False
.WorkerSupportsCancellation = False
AddHandler .DoWork, AddressOf BGW_DoWork
AddHandler .RunWorkerCompleted,
AddressOf BGW_RunWorkerCompleted
End With
End Sub
Private Sub pFileArrived(sender As Object, e As FileSystemEventArgs)
pNotifyClient(e.FullPath)
End Sub
Private Sub pNotifyClient(sFullPath As String)
If Not BGW.IsBusy Then
BGW.RunWorkerAsync(sFullPath)
Else
MyQueue.Enqueue(sFullPath)
End If
End Sub
Private Sub BGW_DoWork(ByVal sender As Object,
ByVal e As DoWorkEventArgs)
'...
End Sub
But how can I find out, when the BGW is done?
I know, that there is the RunWorkerCompleted event. However, this event is fired from a real BGW instance still existing, so I can not go on and simply call it again from within the event handler.
Private Sub BGW_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs)
'This won't work.
If MyQueue.Count > 0 Then
BGW.RunWorkerAsync(MyQueue.Dequeue)
End If
End Sub
What is the proper way of doing such things? Initializing a timer does spring to mind, but it doesn't seem right. (How much time should I give it? Should I loop for the BGW thread's end?)
Or should I consider another approach than invoking a BGW?
If you want to nuke this problem from the orbit, put an AutoResetEvent(false) somewhere and your BGW's last task should be to evt.Set() and your main thread can do evt.WaitOne(0) to just query the status. If there's a possibility of running multiple BGWs at the same time, you need some data structure to keep track of which ARE is associated with which BGW.
A larger investment would be switch to pure producer-consumer design, which involves a queue (which you already have in a form) and consumer thread(s) to dequeue work items and process them.

Making sure async tasks complete before vb.net application terminates

I'm creating a vb.net desktop application. This application includes some asynchronous functions. When the user closes the application via the red X in the upper-right corner, there is some logic to possibly run one or more of these async functions. The problem is, the program terminates before they are complete. I figured using "Await" in my call would do that, but apparently not.
I found this thread that talks about using ManualResetEvent, but I'm having trouble understanding all of it, especially since the question is in the context of a console app, and the MSDN documentation the answer links to is about specifying threads, not simply using async tasks. As an attempt at using it anyway, I tried adding this to my main form:
Public resetEvent As ManualResetEvent = New ManualResetEvent(False)
And immediately after the call to one of these functions, I added this (quote includes the call):
Await activeCount.SerializeAsync(activeCount)
resetEvent.WaitOne()
And at the end of my async function itself, before returning the Task, added this:
frmMain.resetEvent.Set()
I don't think I'm using that right, though. The program still terminates before it's complete anyway.
Even before that, I figured the best place for such a thing would be in ApplicationEvents MyApplication_Shutdown, but I'm not sure how to know if such a function is still running at that point.
So what is the best way to make sure all my async functions complete before the application terminates in this situation?
Thank you!
UPDATE AFTER ACCEPTED ANSWER:
Though F0r3v3r-A-N00b's answer worked, I realized I need to use a dialog in certain cases. I couldn't call that within the background worker because the dialog is on the GUI thread, not the background thread. I tried moving things around so I'd call the dialog first, then make the background worker and all that, but for whatever reason I couldn't get it to work.
Long story short, I got around it by simply making a synchronous version of my functions, and so I could say 'if the user terminated the program and I need to call any of these functions before closing, call the synchronous versions instead'. That works. Thanks!
Try this. Create a new project. Add 1 label and backgroundworker to your form. Paste this in your form's code area:
Public Class Form1
Dim taskCompleted As Boolean = False
Dim taskIsrunning As Boolean = False
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Threading.Thread.Sleep(5000)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
taskCompleted = True
taskIsRunning = False
Label1.Text = "Background task completed."
Me.Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If taskIsRunning Then
e.Cancel = True
Exit Sub
End If
If Not taskCompleted Then
taskIsRunning = True
Label1.Text = "Starting background task."
BackgroundWorker1.RunWorkerAsync()
Label1.Text = "Background task is running."
e.Cancel = True
End If
End Sub
End Class

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

VB.NET Cross-thread operation not valid

I have a loop (BackgroundWorker) that is changing a PictureBox's Location very frequently, but I'm getting an error -
Cross-thread operation not valid: Control 'box1' accessed from a thread other than the
thread it was created on.
I don't understand it at all, so I am hoping someone can help me with this situation.
Code:
box1.Location = New Point(posx, posy)
This exception is thrown when you try to access control from thread other than the thread it was created on.
To get past this, you need to use the InvokeRequired property for the control to see if it needs to be updated and to update the control you will need to use a delegate. i think you will need to do this in your backgroundWorker_DoWork method
Private Delegate Sub UpdatePictureBoxDelegate(Point p)
Dim del As New UpdatePictureBoxDelegate(AddressOf UpdatePictureBox)
Private Sub UpdatePictureBox(Point p)
If pictureBoxVariable.InvokeRequired Then
Dim del As New UpdatePictureBoxDelegate(AddressOf UpdatePictureBox)
pictureBoxVariable.Invoke(del, New Object() {p})
Else
' this is UI thread
End If
End Sub
For other people which coming across this error:
Try the dispatcher object: MSDN
My code:
Private _dispatcher As Dispatcher
Private Sub ThisAddIn_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
_dispatcher = Dispatcher.CurrentDispatcher
End Sub
Private Sub otherFunction()
' Place where you want to make the cross thread call
_dispatcher.BeginInvoke(Sub() ThreadSafe())
End Sub
Private Sub ThreadSafe()
' here you can make the required calls
End Sub