Thread to update DataGridView - vb.net

I have a windows form with a button and a datagridview. When I select the button a thread is initiated that does something and eventually fills my datagridview. The problem is that the datagridview is not filled.
What am I doing wrong?
My code:
Private MyCreateStructureFromSampleXMLTread As Thread
Private Sub ButtonCreateStructureFromSampleXML_Click(sender As Object, e As EventArgs) Handles ButtonCreateStructureFromSampleXML.Click
Me.Cursor = Cursors.WaitCursor
MyCreateStructureFromSampleXMLTread = New Thread(AddressOf ModuleXML_MESSAGE_STRUCTURE.CreateStructureFromSampleXML)
MyCreateStructureFromSampleXMLTread.IsBackground = True
MyCreateStructureFromSampleXMLTread.SetApartmentState(ApartmentState.STA)
MyCreateStructureFromSampleXMLTread.Start()
Me.Cursor = Cursors.Default
End Sub
Threading:
Friend Sub CreateStructureFromSampleXML()
FormUTool.Cursor = Cursors.WaitCursor
Try
If ModuleFileHandling.OpenFile(Application.StartupPath, "Select XML File", ".xml", "XML File (*.xml)|*.xml") = True Then
ParseXMLFile(PublicUToolVariable.MyOpenedFile)
End If
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical)
End Try
ModuleXML_MESSAGE_STRUCTURE.AlignElementPaths(PublicUToolVariable.MyAccessConnection)
ModuleXML_MESSAGE_STRUCTURE.XML_MESSAGE_STRUCTUREFillDataGridView(PublicUToolVariable.MyAccessConnection)
FormUTool.Cursor = Cursors.Default
End Sub

You are trying to fill the DataGridView from a thread other than the UI thread. That is what you are doing wrong. UI elements have thread affinity requirements that mandate that they are only ever accessed from the thread that created them.
Change your strategy so that you are loading and parsing the XML in a worker thread and then send that data to the UI thread and let the UI thread fill the DataGridView. That is the standard approach. You can use the BackgroundWorker class. The DoWork event handler will execute on a worker thread and the RunWorkerCompleted event handler will run on the UI thread so all of the marshaling is handled for you.

Related

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.

Properly closing a form running a loop in a new thread

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.

Vb.net Interact with control on UI thread from another thread - InvokeRequired and HandleCreated false

I want to interact with a control on my main Form (Ui thread).
But it is not working, and I need your help!
I use the following code on my main Form:
delegate sub AddObjectsToFolvDelegate(byref list As List(Of source)) 'delegate
public Sub AddObjectsToFolv(byref value As List(Of source)) 'invoke
Try
msgbox(folvsource.IsHandleCreated.ToString) 'Always false, on UI thread true
If me.InvokeRequired Then 'Always false
Dim d As New AddObjectsToFolvDelegate(AddressOf AddObjectsToFolv)
me.invoke(d, value)
Else
me.folvsource.AddObjects(value) 'Add Objects to Objectlistview
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
And on the other Class I use the following code to call the invoke:
FrmMain.AddObjectsToFolv(proxysites)
If I check if a invoke is requiered to access the control from a worker thread it always returns false. So if I then check if the handle is created it also returns false (on worker thread).
The thread was started from my main Form after it was compleatly loaded (shown).
I also tried to manually create my control with folvSource.CreateControl()
If I call the invoke I get the error message (cause the handle of the control(s) is not created inside my worker thread):
invoke or begininvoke cannot be called on a control until the window handle has been created
I create new threads using the following code with the SmartThreadPool libray on my main Form:
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
'msgbox(import.TestAdd("").ToString)
dim imp as New Import
dim MajorPool As New SmartThreadPool
MsgBox(
MajorPool.QueueWorkItem(New WorkItemCallback(AddressOf imp.TestAdd), "").GetWorkItemResult.GetResult().
ToString())
End Sub

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

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.

ProgressBar freezes when using multithreading

I have a ProgressBar that uses the marquee style when a report is being generated. The reason I am doing this is because the ReportViewer control I use takes some time to generate the report thus making the form unresponsive. I generate the report using a thread so the ProgressBar can show that the program is working. However, when I start the thread the ProgressBar freezes. I have already tried the BackgroundWorker but that didn't work so I used my own threading.
The reason I use the Invoke() method is because I can't make changes to the ReportViewer control on the thread I created because it was created on the UI thread.
The method that takes the most time processing is the RefreshReport() method of the ReportViewer control which is why I'm trying to do that on its own thread instead of the UI thread.
Any help would be appreciated. Thanks.
Here is the code for my thread variable:
Private t As New Thread(New ParameterizedThreadStart(AddressOf GenerateReport))
Here is the code for the button that generates the report:
Private Sub btnGenerateReport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGenerateReport.Click
pbReports.Style = ProgressBarStyle.Marquee
If t.ThreadState = ThreadState.Unstarted Then
t.IsBackground = True
t.Start(ReportType.Roads)
ElseIf t.ThreadState = ThreadState.Stopped Then
t = Nothing
t = New Thread(New ParameterizedThreadStart(AddressOf GenerateReport))
t.IsBackground = True
t.Start(ReportType.Roads)
End If
End Sub
Here is the code that generates the report:
Public Sub GenerateReport(ByVal rt As ReportType)
If rvReport.InvokeRequired Then
Dim d As New GenerateReportCallBack(AddressOf GenerateReport)
Me.Invoke(d, New Object() {rt})
Else
rvReport.ProcessingMode = ProcessingMode.Remote
rvReport.ShowParameterPrompts = False
rvReport.ServerReport.ReportServerUrl = New Uri("My_Report_Server_URL")
rvReport.ServerReport.ReportPath = "My_Report_Path"
rvReport.BackColor = Color.White
rvReport.RefreshReport()
End If
If pbReports.InvokeRequired Then
Dim d As New StopProgressBarCallBack(AddressOf StopProgressBar)
Me.Invoke(d)
Else
StopProgressBar()
End If
End Sub
Your code is starting a new thread from the UI thread. The new thread then immediately marshals back to the UI thread using Invoke - so basically it's as if you hadn't made it multithreaded at all.
Instead of that, make the new thread do all the background processing it can and only marshal back to the UI for parts of the process that need to update the UI.
You can try using the ThreadPool to generate a new worker thread. I use the below in a WPF application to show a loading screen for anything that takes over 4 seconds or so.
You might need to change some of the syntax as I'm a C# guy...
Private Sub btnGenerateReport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGenerateReport.Click
pbReports.Style = ProgressBarStyle.Marquee
ThreadPool.QueueUserWorkItem(Function(th) Do
GenerateReport(ReportType.Roads)
Dispatcher.BeginInvoke(DispatcherPriority.Normal, DirectCast(Function() Do
StopProgressBar()
End Function, Action)
End Function)
End Sub
Also, I believe that the Dispatcher.BeginInvoke is only in WPF and not in WinForms, so you my need to change that back to Me.Invoke or something.