Handling Cross Thread Exception in Vb.Net Forms - vb.net

I have a windows form on to which i have declared custom controls ( label, panels text boxes), The issue is I am loading images of that control in local thread , but some of the pictures not not downloading from web stream, hence the exception, Now i am setting image property Nothing in Catch block, and trying to set other UI panels properties, but it throws exception,
Exception :
"Cross thread operation not valid: Control "XXXXXXXXXX" accessed from a thread other than the thread it was created. "
kindly somebody tell the simplest way to set the property in case of exception occur so I may set the properties of other controls.

Basically, you don't touch controls in methods executed on a secondary thread. Only touch controls on the UI thread. Where you might usually just do this:
myPictureBox.Image = myImage
you now write a method like this:
Private Sub SetPictureBoxImage(img As Image)
If myPictureBox.InvokeRequired Then
myPictureBox.BeginInvoke(New Action(Of Image)(AddressOf SetPictureBoxImage), img)
Else
myPictureBox.Image = img
End If
End Sub
and then call it on the secondary thread instead of setting the Image property directly:
SetPictureBoxImage(myImage)
Note that that method will succeed whether it's called on the UI thread or a secondary thread so you can call it whether you know that you're on a secondary thread or not.
Check this out for more information.
EDIT
Private Sub UpdateUI(img As Image, visible As Boolean)
If Me.InvokeRequired Then
Me.BeginInvoke(New Action(Of Image, Boolean)(AddressOf UpdateUI), img, visible)
Else
myPictureBox.Image = img
myPanel.Visible = visible
End If
End Sub
Note that I used the InvokeRequired and Invoke members of the form instead of a specific control. It actually doesn't matter which form or control those are members of as long as they are owned by the same UI thread but to me it seems logical to use the same control your updating if there's only one or else use the form.
Note also that the signature of the delegate changes to match the signature of the method so that they have the same number and type of parameters.

I have done that UI change in this function:MethodInvoker
Try
'Code that was throwing exception here
Catch ex As Exception
Me.Invoke(New MethodInvoker(Sub()
'All UI changes made here
End Sub))
End Try

Control.CheckForIllegalCrossThreadCalls = False
Will allow you to change your TextBox,Label and etc... in execution

Related

Check if element does exist in WebBrowser using BackgroundWorker

I'm using background worker to manipulate some of the elements on my WebBrowser using vb.net.
For example the background worker RFIDReader will check if I'm on a specific link.
ElseIf TerminalBrowser.Url.ToString.Contains(baseUrl + "box-office/ticket-verification") And rf_code <> "" Then
' Insert RF Code in "rf_code" hidden text field
Me.Invoke(Sub() Me.TerminalBrowser.Document.GetElementById("rf_code").SetAttribute("value", rf_code.ToLower))
End If
What happens here is, if I tap my RFID card. It will include that corresponding value to my rf_code element in my browser.
Now what I want to happen is, I want to check if the container itself (synchronize-rfid) does exist (Since it's a pop up). See image for reference.
Here's our code for that.
If Me.TerminalBrowser.Document.GetElementById("synchronize-rfid") IsNot Nothing Then
' Code here
end if
Reference: https://stackoverflow.com/a/2022120/1699388
The problem is, BackgroundWorker does not really interacts with UI as per the code above.
Is there any method for me to determine if that element exist using background worker?
I think I've done this and it does not work though.
Dim synchronize_rfid = Me.Invoke(Sub() Me.TerminalBrowser.Document.GetElementById("synchronize-rfid"))
If synchronize_rfid IsNot Nothing Then
' Code here
end if
Try declaring the variable first, then set it during the invocation (which will set it from the main thread).
The Sub() lambda behaves like a normal Sub()-End Sub method, which means you can do the same things in there as if you had a separate method.
This works for me:
Dim synchronize_rfid As HtmlElement
If Me.InvokeRequired = True Then
Me.Invoke(Sub() synchronize_rfid = Me.TerminalBrowser.Document.GetElementById("synchronize-rfid"))
Else
synchronize_rfid = Me.TerminalBrowser.Document.GetElementById("synchronize-rfid")
End If
If synchronize_rfid IsNot Nothing Then
' Code here
End If

Updating Variable in Multithreading in VB.NET

I've wrote a program which on startup loads the computer list from Active Directory. This takes about 10 seconds. If the user has started the program with a specific host as parameter, it should be usable immediately.
So to don't interrupt the user I want to load the computer list in a different thread. The problem is that it writes to a variable (the computer list) which is also used in the main thread.
You may think, I could simply use a temporary variable and when its done overwrite the main variable. But I have to keep existing data of the main variable.
'hosts list
Private Shared hosts As New SortedDictionary(Of String, HostEntry)
'Get all computers in Active Directory
'Will run in a extra thread
Private Delegate Sub GetADcomputersDelegate()
Private Sub GetADcomputers()
If Me.InvokeRequired Then
Me.Invoke(New GetADcomputersDelegate(AddressOf GetADcomputers), Nothing)
Else
lblStatusAD.Text = "Getting Computers..."
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End If
End Sub
So when GetADcomputers() runs in its own thread, the main thread is also blocked. I guess because auf the hosts variable.
So what could I change to make the thread do it's work and after that apply the updated computer list without losing data of entries in old hosts list? And all this in a fast and efficient way.
That code is very wrong. If you call that method on a secondary thread then it immediately marshals a call back to the UI thread and does EVERYTHING on the UI thread. What you should be doing is executing all the background work on the secondary thread and then marshalling to the UI thread ONLY to update the UI.
Get rid of that If...Else block and just make the entire body of the method what's current ly in the Else block. Next, identify all the lines that specifically interact with the UI and remove each of those to their own method. You then add If...Else blocks to each of those methods so that only the code that actually touches the UI is executed on the UI thread.
Here's a start:
Private Sub GetADcomputers()
UpdateStatusADLabel("Getting Computers...")
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End Sub
Private Sub UpdateStatusADLabel(text As String)
If lblStatusAD.InvokeRequired Then
lblStatusAD.Invoke(New Action(Of String)(AddressOf UpdateStatusADLabel), text)
Else
lblStatusAD.Text = text
End If
End Sub

How to update main form from thread utilizing a module creates new mainform?

So my use for a module is so I can use the same functions across different programs that I develope for my employer. They also want my module to be distributed amongst other programmers so they can use it as well. The programs need to know when there is a thread still running (SQL code is running (there are no problems with the sql side) and it needs to notify the user when all work is done but the user needs to be able to queue work)
From the main form I am using this code:
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start()
This is the code for my module:
Public Sub Testing()
Form1.threadsrunning += 1
Form1.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
Form1.threadsrunning -= 1
Form1.accesscontrolsmoduletesting()
end sub
The code to access the controls on the main form is
Public Sub accesscontrolsmoduletesting()
If Me.InvokeRequired = True Then
Me.Invoke(New MethodInvoker(AddressOf accesscontrolsmoduletesting))
Else
If threadsrunning > 0 Then
Label4.Text = threadsrunning & " threads running"
Else
Label4.Text = "0 threads running"
End If
End If
End Sub
I already know the issue is the new thread is creating a new form. I tested this by showing the form and making it wait so it didnt immediately dispose itself and I seen the label was updated. How do I make this thread update the main form instead of just creating a new mainform and then disposing itself after the thread dies?
To reiterate on my Comment you need to get the actual Form1 that is being shown, you should change your Testing Method to accept a Parameter of Form1, then you can use a Parameterized Thread.Start to pass in the Calling Form. You are running into a feature that was left in place to placate Vb6 programmers transitioning to VB.net as this answer by Hans states. And you may find this Blog Post by John Mcllhinney an interesting read.
From Second Link(emphasize mine):
In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.
So in response to above I would change your Module Test Method to:
Public Sub Testing(myForm As Form1)
myForm.threadsrunning += 1
myForm.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
myForm.threadsrunning -= 1
myForm.accesscontrolsmoduletesting()
End Sub
And I would change your Form1's Thread Start Code to look like this.
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start(Me) 'Note the passing in the instance of the calling Form
After making these few changes your code will work

Newbie: Invalid cross thread access with using busyindicator (silverlight and wcf service)

I have a problem with the busyindicator control of silverlight.
I have a datagrid (datagrid1) with its source set to a wcf service (client).
I would like to use the busyindicator control (bi) of silvelright toolkit when the datagrid loads itself.
But I have an "Invalid cross thread access" when I use "ThreadPool".
Sub LoadGrid()
Dim caisse As Integer = ddl_caisse.SelectedValue
Dim env As Integer = ddl_env.SelectedValue
bi.IsBusy = True
ThreadPool.QueueUserWorkItem(Sub(state)
AddHandler client.Get_PosteSPTCompleted, AddressOf client_Get_PosteSPTCompleted
client.Get_PosteSPTAsync(caisse, env)
Dispatcher.BeginInvoke(Sub()
bi.IsBusy = False
End Sub)
End Sub)
End Sub
Private Sub client_Get_PosteSPTCompleted(sender As Object, e As ServiceReference1.Get_PosteSPTCompletedEventArgs)
DataGrid1.ItemsSource = e.Result ' Here, Invalid cross thread access
End Sub
I know that the datagrid control doesn't exist in the "new thread", but how have to I make to avoid this error?
Thank you.
William
First point: Why are you using the ThreadPool?
Using a ThreadPool would be a good idea if your service was synchronous, but your WCF service is asynchronous: it won't block your UI thread after being called using Get_PosteSPTAsync.
Second point: there seems to be an issue with your IsBusy property. You're first setting it to true, then you starts an operation asynchronously, then you set it to false immediately. You should set it to false in the Completed handler.
Third point: the client_Get_PosteSPTCompleted handler won't be executed in the same thread as the UI thread (even if you don't use the ThreadPool). That's why you'll have to use the dispatcher here so force using the UI thread.
Your DataGrid1.ItemsSource = e.Result must be modified to:
Dispatcher.BeginInvoke(Sub()
DataGrid1.ItemsSource = e.Result ' Fixes the UI thread issue
bi.IsBusy = False ' Sets busy as false AFTER completion (see point 2)
End Sub)
(note sure about the VB.Net syntax, but that's the idea)
Last point: I don't know about the client object lifetime, but you're adding a new handler to the Get_PosteSPTCompleted each time you call LoadGrid. Maybe you could think of detaching handlers after use.

Why am I getting a "Cross-thread operation not valid exception" in the callback method when using AsyncCallback with BeginInvoke?

I have a windows form that gets data from a scale via the serial port. Since I need the operator to be able to cancel the process I do the get data process on a second thread.
The data collection process gets multiple readings from the scale one at a time. The form has a label that needs to be updated with information specific to each reading.
I call the method to get the data from the scale with this code.
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
The delegate for the readALine method is defined in the UI code.
Delegate Function readALine(ByVal toSend As String) As String
The GetDataCallback method:
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
.
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
.
End Sub
I get the exception on the "lblInstructions.Text =" statement.
I thought the GetDataCallback method was part of the UI thread so don't understand why I'm getting the exception. I know this could probably be rewritten using a BackgroundWorker and it's appropriate events but for now would like to understand why this isn't working as expected.
The application was written originally in VS2003 and just recently upgraded to VS2008.
Any insight will be appreciated.
Thanks,
Dave
The problem is a confusion over BeginInvoke. Calling Control.BeginInvoke marshals a delegate call to the UI thread. Calling BeginInvoke on a delegate causes it to be executed on a thread pool thread - and any callback you provide is executed on the same (thread pool) thread. The latter is what you're doing, which is why GetDataCallback is being executed on a thread pool thread, not the UI thread..
So, within GetDataCallback you need to call Control.Invoke or Control.BeginInvoke to marshal back to the UI thread.
One point to note: I very rarely use Control.InvokeRequired these days - it's simpler to unconditionally call Invoke/BeginInvoke; the performance difference isn't usually enough to make up for the benefit in readability, in my experience.
user-interface controls can only be updated by the thread that created them
try
yourForm.BeginInvoke
instead; that should marshall the call to the correct thread
In .NET 1.1 it was possible to perform these cross-thread operations, even though they weren't safe.
In .NET 2.0 the exception you mention is thrown, when you try to perform cross-thread operations such as this, which means that you are performing the UI operations on a non-UI thread, even though you thought you weren't.
Try using the Me.InvokeRequired method to determine whether you are currently on the UI thread. E.g. you could define a method to perform the necessary tasks:
Protected Sub PerformUIOperations()
If Me.InvokeRequired Then
'We are currently on a non-UI thread. Invoke this method on the UI thread:
Me.Invoke(New MethodInvoker(AddressOf Me.PerformUIOperations))
Return
End If
'Perform UI operations when we know it is safe:
lblInstructions.Text = "blabla"
End Sub
The PerformUIOperations method can then be called from any non-UI thread, since it takes care of performing the tasks on the correct thread itself.
Of course, if you need to pass parameters to the PerformUIOperations method (such as information regarding the ongoing operation) you'll have to define a delegate to match the PerformUIOperations method signature and use this instead of the MethodInvoker.
Jon,
I have the following in another section of the code:
Delegate Sub setValueCallback(ByVal row As Integer, ByVal value As Decimal)
Public Sub setValue(ByVal row As Integer, ByVal value As Decimal)
If Me.Controls.Item(_tbIndex(row - 1)).InvokeRequired Then
Dim d As New setValueCallback(AddressOf setValue)
Me.Invoke(d, New Object() {row, value})
Else
Dim tb As TextBox = Me.Controls.Item(_tbIndex(row - 1))
tb.Text = value
_dt.Rows(tb.Tag).Item(1) = value
End If
End Sub
How would this be rewritten to not use .InvokeRequired?
Dave, maybe this is the solution you are looking for:
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
Delegate Function readALine(ByVal toSend As String) As String
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
If lblInstructions.InvokeRequired Then
lblInstructions.Invoke(New AsyncCallback(AddressOf GetDataCallback), New Object() {ia})
Else
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
End If
End Sub