VB.NET Cross-thread operation not valid - vb.net

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

Related

Threads are the bane of my existence

I have a button which on click will run a Sub, creating a process which runs a script.
When this script is finished an Exited handler will fire and run another Sub which cleans up so that the application is ready to go anew without restarting it.
I disable the button during the run and try to re-enable it when the Exit is fired, however it tells me that the button is in another thread. So I tried using SynchronizedContext and Post:
Declared at the start of my class:
Class MainWindow
Private sc As SynchronizationContext = SynchronizationContext.Current
Not sure if I'm doing that correctly but it worked for me elsewhere in the code where I had the same problem.
The exit handling sub:
Private Sub CMD_Exited(ByVal sender As Object, ByVal e As EventArgs)
myProcess.CancelOutputRead()
myProcess.CancelErrorRead()
sc.Post(AddressOf Button_Click, Button1.IsEnabled = True)
Close()
End Sub
Which errors:
Method 'Private Sub Button_Click(sender As Object, e As RoutedEventArgs)' does not have a signature compatible with delegate 'Delegate Sub SendOrPostCallback(state As Object)'.
What can I do here? Changing the button signature will cause incompatibilities elsewhere.
Are there better ways to get around this threads issue?
Visual Vincent is correct, you need to invoke on the UI thread. Specifically you need to read this How to: Make Thread-Safe Calls to Windows Forms Controls.
Public Delegate Sub DoProcessStuffOnUIThreadHandler()
Private Sub CMD_Exited(ByVal sender As Object, ByVal e As EventArgs)
If Me.Button1.InvokeRequired Then
Dim d As New DoProcessStuffOnUIThreadHandler(AddressOf DoProcessStuffOnUIThread)
Me.Button1.Invoke(d)
Else
DoProcessStuffOnUIThread()
End If
End Sub
Private Sub DoProcessStuffOnUIThread()
myProcess.CancelOutputRead()
myProcess.CancelErrorRead()
Button1.IsEnabled = True
Close()
End Sub
(28-SEP-2017) Edit to add an alternative, that I used frequently in my WinForms code days, for brevity:
Public Delegate Sub DoProcessStuffOnUIThreadHandler()
Private Sub CMD_Exited(ByVal sender As Object, ByVal e As EventArgs)
If Me.Button1.InvokeRequired Then
Dim d As New DoProcessStuffOnUIThreadHandler(AddressOf CMD_Exited)
Me.Button1.Invoke(d)
Else
myProcess.CancelOutputRead()
myProcess.CancelErrorRead()
Button1.IsEnabled = True
Close()
End If
End Sub
The added example simply reduces code use. Both examples end in the same result. Hope that helps.

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

backgroundworker throws "An error occurred creating the form..."

I have main forms that has a button that opens a master-customer on datagrid. I use dataview for the purpose of filtering the data using dataview.rowfilter.
The problem is, during the form load. It takes 5-6 seconds (the program is unresponsive during that time). What I'm trying to do is to load the data to the dataview on the background and show it on the gridview on workercompleted.
it gave me this error: "An error occurred creating the form. See Exception.InnerException for details. The error is: Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it." --> on dowork
I read somewhere that i should use Invoke. But i don't know how to use it.
here is my code:
Private Sub custcall_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
TBfind.Enabled = False
SetMyCustomFormat("yyyy-MM-dd HH:mm:ss")
BWcustload.RunWorkerAsync()
End Sub
Private Sub BWcustload_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BWcustload.DoWork
mydataview = New DataView(datatablecust)
End Sub
Private Sub BWcustload_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BWcustload.RunWorkerCompleted
DGVcustomer.DataSource = mydataview
TBfind.Enabled = True
End Sub
Have you tried moving the code to the event Form_Shown?
Also, have you tried putting the code in your DoWork section inside a SyncLock?
Try this:
SyncLock mydataview
mydataview = New DataView(datatablecust)
End SyncLock
As for the STA model ... try adding this to your code and set the startup object to Sub Main()
<STAThread()> _
Public Shared Sub Main()
Dim mainForm As New custcall()
Application.Run(mainForm)
End Sub
EDITED:
As far as invoke is concerned. ... that would definitely work, too ... but I don't know that it would have solved the STAThread issue.
To use invoke, first you have to declare a delegate sub in your form:
Delegate Sub LoadDataCallback()
Declare a function to take care of the actual loading of the data:
Private Sub LoadData()
mydataview = New DataView(datatablecust)
End Sub
Then, you would start a new thread in your Shown event (or Load event):
Dim mythread As New Thread(Sub()
Dim callLoad As New LoadDataCallBack(LoadData)
Me.Invoke(callLoad)
End Sub)
mythread.Start()
This gets around having to use SyncLock (although in some cases you may want to if it doesn't end up working correctly).
Don't forget to add Imports System.Threading to the top of your code file.

Cross Thread invoke from class ? Confused - vb.net

maybe I am being stooped... but the fact is that I am a bit of a n00b concerning threading...
I am making use of a serial port in a class. I am raising an event from that class to my form calling the class. Event contains data received...
I wish to simply populate a textbox from the raised event.
Now I am not specifically creating a seperate thread, but I get the normal crossthreading error when trying to update my textbox on the UI, so my assumption is that the serial port and its internal methods probably creates its own threads...
Regardless, I am a bit confused as to how to properly implement an invoke, from my main form, pointing to the thread in the instantiated class...
I hope this makes sense...
Dim WithEvents tmpRS232 As New clsRS232
Private Sub but_txt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles but_txt.Click
tmpRS232.Set_com_port("COM8", 38400)
tmpRS232.Transmit_data(txt_tx.Text)
End Sub
Private Sub tmprs232_rx_data_returned(ByVal str_data As String) Handles tmpRS232.rx_data_returned
txt_rx.Text = str_data 'Cross threading error
MsgBox(str_data) 'Fires without errors
End Sub
Can someone please provide a code example based on this code?
thanks.
You are correct, the issue here is that you are attempting to update a UI element from a non-UI thread (in this case the serial port handler). What you need to do is check if the InvokeRequired flag is set on the control that you are trying to access from the callback. If so that means that you need to marshall your call to the UI thread. You can achieve this by using either Invoke or BeginInvoke from System.Windows.Forms.Control.
Private Delegate Sub SetRxTextCallback(ByVal [text] As String)
Private Sub SetRxText(ByVal [text] As String)
txt_rx.Text = [text]
End Sub
Private Sub tmprs232_rx_data_returned(ByVal str_data As String) Handles tmpRS232.rx_data_returned
If (txt_rx.InvokeRequired) Then
Dim d As New SetRxTextCallback(AddressOf Me.SetRxText)
Me.BeginInvoke(d, New Object() {[str_data]})
End If
'txt_rx.Text = str_data 'Cross threading error
'MsgBox(str_data) 'Fires without errors
End Sub
Here's a link to the MSDN documentation that explains it in detail.
Or simply...
Private Sub tmprs232_rx_data_returned(ByVal str_data As String) Handles tmpRS232.rx_data_returned
If InvokeRequired Then
Invoke(Sub()txt_rx.Text = str_data)
Else
txt_rx.Text = str_data
End If
End Sub