I am trying to understand how delegates and invoke work.
So I build a form, with a label and a button.
When someone clicks on the Button, the Text changes to "Stop" and a counter starts counting up. This counter should be displayed on a Label. This is my code:
Public Class Form1
Private t1 As Thread
Private sek_ As Integer = 0
Private Sub btn_read_Click(sender As Object, e As EventArgs) Handles btn_read.Click
If btn_read.Text = "Read" Then
btn_read.Text = "Stop"
t1 = New Thread(AddressOf stopw_)
t1.Start()
Else
lbl_stopw_.Text = ""
btn_read.Text = "Read"
End If
End Sub
Private Delegate Sub stopw_D()
Private Sub stopw_()
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
Else
lbl_stopw_.Text = sek_
End If
Thread.Sleep(1000)
Loop
sek_ = 0
If t1.IsAlive Then t1.Abort()
End Sub
End Class
If I start debugging, the form still freezes and the label does not get updated. If I delete all the delegate and invoke stuff and use Me.CheckForIllegalCrossThreadCalls = False Its working.
What am I doing wrong?
Control.Invoke() is used to execute a method on the same thread that the control was created on. Since code can only be executed one line at a time in each thread, executing a method on the control's thread will result in the method being "queued" until the other code in that thread, prior to the method, has completed. This makes it thread-safe as there will be no concurrency issues.
A Delegate is simply a class holding the pointer to a method. It exists so that you can use methods as if they were ordinary objects (in this case you pass it to a function). The AddressOf operator is a quick way of creating a delegate.
Now, you have a few issues in your code. First of all, you should not try to access or modify ANY UI element from a background thread. Whenever you want modify or check a control you must always invoke.
More specifically, I'm talking about your While-loop:
'You can't check the button here without invoking.
Do While btn_read.Text = "Stop"
It is better if you create a Boolean variable that indicates when the thread should run.
Private t1 As Thread
Private sek_ As Integer = 0
Private ThreadActive As Boolean = False
Set ThreadActive to True before you start the thead, then in your thread's While-loop check:
Do While ThreadActive
Now, there is another issue. Your UI freezes because of this:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New stopw_D(AddressOf stopw_))
NEVER invoke the the same method which the thread runs on! Doing so starts the processing all over again, but on the UI thread. Your loop makes the UI thread completely busy, which is why it doesn't redraw itself.
So when you are to update something, always invoke a seperate method. If you target .NET 4.0 or higher you can use lambda expressions for a quick, inline delegate:
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Else
lbl_stopw_.Text = sek_
End If
However if you are targeting .NET 3.5 or lower you have to stick to the normal way of using delegates:
'Outside your thread.
Private Delegate Sub UpdateLabelDelegate(ByVal Text As String)
Private Sub UpdateLabel(ByVal Text As String)
lbl_stopw_.Text = Text
End Sub
'In your thread.
If lbl_stopw_.InvokeRequired Then
lbl_stopw_.Invoke(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Else
UpdateLabel(sek_)
End If
Alternatively, in order to minimize the amount of code you have to write you can create an extension method to do the invoking for you:
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Sub InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object())
If Parameters Is Nothing OrElse _
Parameters.Length = 0 Then Parameters = Nothing 'If Parameters is null or has a length of zero then no parameters should be passed.
If Control.InvokeRequired = True Then
Control.Invoke(Method, Parameters)
Else
Method.DynamicInvoke(Parameters)
End If
End Sub
End Module
Usage, .NET 4.0 or higher:
lbl_stopw_.InvokeIfRequired( _
Sub()
lbl_stopw_.Text = sek_
End Sub)
Usage, .NET 3.5 or lower:
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
When using this extension method you don't need to write InvokeRequired checks everywhere:
Do While btn_read.Text = "Stop"
sek_ = sek_ + 1
lbl_stopw_.InvokeIfRequired(New UpdateLabelDelegate(AddressOf UpdateLabel), sek_)
Thread.Sleep(1000)
Loop
And finally, this is just unnecessary:
If t1.IsAlive Then t1.Abort()
The thread will always be alive when it reaches that If-statement since it hasn't exited the stopw_ method yet. But once the thread has exited the method it will end normally, so there's no reason to call Abort().
The answer got a bit long, but I hope it to be helpful!
Related
I use BackgroundWorkers occasionally to update another thread while not freezing the UI. I use this code often as it works well:
Private Delegate Sub DelegateUpdateStatus(ByVal statusText As String)
Private Sub UpdateStatus(ByVal statusText As String)
If InvokeRequired Then
Invoke(Sub() LblStatus.Text = statusText)
Else
LblStatus.Text = statusText
End If
End Sub
I understand this code but I do have trouble understanding how I can use this code or something like it for ListViewMain.BeginUpdate, ListViewMain.EndUpdate and ListViewMain.Items.Add.
Can someone guide me in the right direction?
Firstly, your delegate type is useless because you're not using it. You're using a Lambda expression to create a delegate so your DelegateUpdateStatus type is pointless. Secondly, you should be recalling the same method in the If block and then doing the actual work only once, in the Else block:
Private Sub UpdateStatus(ByVal statusText As String)
If InvokeRequired Then
Invoke(Sub() UpdateStatus(statusText))
Else
LblStatus.Text = statusText
End If
End Sub
The actual work to be done on the UI thread is done only in the Else block, so you can do whatever you want there, including adding items to a ListView, e.g.
Private Sub AddListViewItems(items As IEnumerable(Of ListViewItem))
If InvokeRequired Then
Invoke(Sub() AddListViewItems(items))
Else
ListViewMain.BeginUpdate()
For Each item in items
ListViewMain.Items.Add(item)
Next
ListViewMain.EndUpdate()
End If
End Sub
I have an application with a DataGridView on which multiple people could be working at the same time. I want to have each user's current row location displayed via a different colour row in the DataGridView.
Previously I was doing all of this updating via the RowEnter event however the performance is not satisfactory, for obvious reasons.
I'm trying to have a background thread which loops every 10 seconds to populate a DataTable with keys of the other users' locations which then references a key column in the DGV, and if they match, change the DGV row background color else set it to the default.
My current code, below, loops every 10s but it doesn't actually update the DGV.
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ActiveThread = True
dgvThread = New Thread(AddressOf UpdateDGVFromThread) With {
.IsBackground = True}
dgvThread.Start()
End Sub
Public Sub UpdateDGVFromThread()
Do While ActiveThread = True
'Sets table with key values
dtUsers = CLS_USERS.GetUsers(User)
'Loop through them
For Each row As DataRow In dtUsers.Rows
intSeq = row("SEQUENCE")
'Loop through each DGV row and compare the values
For Each dgv_row As DataGridViewRow In dgvCandList.Rows
dgvCandList.BeginInvoke(
Sub()
If dgv_row.Cells("CURRENT_CAND_SQ").Value = intSeq Then
dgv_row.DefaultCellStyle.BackColor = Color.DarkCyan
Else
dgv_row.DefaultCellStyle.BackColor = Color.Cyan
End If
End Sub)
Next
Next
Thread.Sleep(10000)
Loop
End Sub
I tried using dgv.Invoke() rather than .BeginInvoke() but this seemed to lock up the UI thread constantly and only the DGV was unlocked.
Can anyone point me in the right direction?
The BeginInvoke method is used to asynchronously invoke a method delegate on the thread that created the Control's handle. The UI thread, here. It's signature is:
Public Function BeginInvoke (method As Delegate) As IAsyncResult
The method Delegate is then declared in the same thread where the Control invoked has been created.
The delegate should then be declared like this:
In the UI thread:
Delegate Sub MyUpdateDelegate()
Public Sub MyUpdateMethod()
[SomeControl].Text = "Updated Text"
End Sub
In another thread:
Private Sub InvokeFromAnotherThread()
'Prefer the Parent Form as marshaller
Me.BeginInvoke(New MyUpdateDelegate(AddressOf MyUpdateMethod))
'(...)
'You can also use a Control, but the Parent Form is better
[SomeControl].BeginInvoke(New MyUpdateDelegate(AddressOf MyUpdateMethod))
End Sub
Using an anonymous method in-place won't cut it.
There's a shortcut, provided by the MethodInvoker delegate:
MethodInvoker provides a simple delegate that is used to invoke a
method with a void parameter list. This delegate can be used when
making calls to a control's Invoke method, or when you need a simple
delegate but do not want to define one yourself.
Using a MethodInvoker delegate, there's no need to declare a delegate in the UI thread. An anonymous method can be used here, it will be invoked in the UI thread:
Private Sub InvokeFromAnotherThread()
'(...)
BeginInvoke(New MethodInvoker(Sub() [SomeControl].Text = "Updated Text"))
'(...)
End Sub
Or:
Private Sub InvokeFromAnotherThread()
'(...)
BeginInvoke(New MethodInvoker(
Sub()
[SomeControl].Text = "Updated Text"
[SomeOtherControl].BackColor = Color.Red
End Sub))
'(...)
End Sub
Why I suggested a Timer:
The thread you're using has one task only: update a Control in the UI thread and then sleep.
To perform this task, it needs to invoke a method in the UI thread. If the reason why the thread has been created is to avoid blocking the UI thread, a Timer will do the same thing. A System.Windows.Forms.Timer, specifically, will raise its Tick event in the UI thread, without cross-thread calls.
The practical effect is more or less the same.
I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus
(something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what
causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else
it keeps running smoohtly, updating a label like it should with the
amount of files found.. and even finish the loop with 100% success
(again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop
called from a button...
2) windows 7 could possibly support a new
thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see
the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can
still see the label refresh BUT on windows 7 everything works the same
UNLESS i click somewhere, no matter where i click on windows the loop
crashes
im using framework 4.6.2 developer
While I'm glad you found a solution, I advise against using Application.DoEvents() because it is bad practice.
Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put, Application.DoEvents() is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages. WM_PAINT is one of those messages which is why your window redraws.
However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the ScanButtonInForm1() method is called by the click of a button you'd be able to click that button again (unless you set Enabled = False) and starting the process once more, which brings us to yet another backside:
The more Application.DoEvents()-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.
The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the Thread class and pass a delegate to the method you want the thread to run:
Dim myThread As New Thread(AddressOf <your method here>)
...then you should set its IsBackground property to True if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).
Then you just call Start() and you have a running background thread!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the Control.Invoke() or Control.BeginInvoke() methods. BeginInvoke() is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.
One should also make sure to check the Control.InvokeRequired property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.
The basic InvokeRequired/Invoke pattern looks like this (mostly for reference, keep reading below for shorter ways):
'This delegate will be used to tell Control.Invoke() which method we want to invoke on the UI thread.
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox, ByVal Text As String)
Private Sub myThreadMethod() 'The method that our thread runs.
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), TextBox1, "Status update!") 'We are in a background thread, therefore we must invoke.
Else
UpdateTextBox(TextBox1, "Status update!") 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
'This is the method that Control.Invoke() will execute.
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox, ByVal Text As String)
TargetTextBox.Text = Text
End Sub
New UpdateTextBoxDelegate(AddressOf UpdateTextBox) creates a new instance of the UpdateTextBoxDelegate that points to our UpdateTextBox method (the method to invoke on the UI).
However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Private Sub myThreadMethod()
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(Sub() TextBox1.Text = "Status update!") 'We are in a background thread, therefore we must invoke.
Else
TextBox1.Text = "Status update!" 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
Now all you have to do is type Sub() and then continue typing code like if you were in a regular method:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End Sub)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and InvokeRequired check for you.
Place this in a separate code file:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
Now you only need to call this single method when you want to access the UI, no additional If-Then-Else required:
Private Sub myThreadMethod()
'Do some background stuff...
Me.InvokeIfRequired(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
End Sub)
'Do some more background stuff...
End Sub
Returning objects/data from the UI with InvokeIfRequired()
With my InvokeIfRequired() extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Example
The following code will increment a counter that tells you for how long the thread has run:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False 'Make the button unclickable (so that we cannot start yet another thread).
End Sub
Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000) 'Wait for approximately 1000 ms (1 second).
Time += 1
Me.InvokeIfRequired(Sub() Label1.Text = "Thread has been running for: " & Time & " seconds.")
End While
End Sub
Hope this helps!
The reason your application is freezing is that you are doing all the work on the UI thread. Check out Async and Await. It uses threading in the background but makes it way easier to manage. An example here:
https://stephenhaunts.com/2014/10/14/using-async-and-await-to-update-the-ui-thread/
I have a thread that runs background jobs and is required to update the GUI once in a while. My program has been designed so that when the user clicks off of a form, the thread and background operations still run, yet the controls have been disposed (for memory management purposes).
I have been using Invoke() and "If Control.Created = True" to make sure that the thread can successfully update the controls without running into any exceptions. However, when the form is recreated, all "Control.Created" values are false and Invoke() fails with "{"Invoke or BeginInvoke cannot be called on a control until the window handle has been created."}"
My guess is that this has something to do with the fact that when the form is recreated it is assigned different handles and that the "Invoke()" is looking at the old handle. SO my question is, how do I fix this?
EDIT: As per requested, the code for opening the form and where the bg thread works from
Opening the DropLogMDIalt form is simply
FormCTRL.Show()
The Background Thread runs when the control is modified so that the NumericUpDown is more than 0 (so that there is something to countdown from)
Private Sub NLauncherTerminateInput_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DLScanInterval.ValueChanged
If DLScanInterval.Created = True Then
DLTimerControlValue = DLScanInterval.Value
If DLTimerControlValue = 0 Then
CancelDropLogTimer()
Else
If DLScanIntervalControl.Active = False Then
BeginDropLogTimer()
End If
End If
End If
End Sub
Public Sub BeginDropLogTimer()
Dim N As New Threading.Thread(AddressOf DropLogTimerIntervalThreadWorker)
N.Start()
DLScanIntervalControl.ThreadID = N.ManagedThreadId
DLScanIntervalControl.Active = True
End Sub
Public Sub CancelDropLogTimer()
DLScanIntervalControl.Active = False
End Sub
Public Sub DropLogTimerIntervalThreadWorker()
DLScanTimerSecondsLeft = DLTimerControlValue * 60
Dim s As Integer = DLTimerControlValue
Do Until 1 = 2
DLScanTimerSecondsLeft = DLTimerControlValue * 60
Do Until DLScanTimerSecondsLeft <= 0
If Not (DLTimerControlValue = 0 Or DLScanIntervalControl.CancelPending = True) Then
Else
Exit Sub
End If
If Not DLTimerControlValue = s Then
DLScanTimerSecondsLeft = DLTimerControlValue * 60
s = DLTimerControlValue
End If
Dim ToInvoke As New MethodInvoker(Sub()
Timer(DLScanTimerSecondsLeft, ":", DLScanIntervalTB)
End Sub)
If (Me.IsHandleCreated) Then
If (InvokeRequired) Then
Invoke(ToInvoke)
Else
ToInvoke()
End If
End If
Threading.Thread.Sleep(1000)
DLScanTimerSecondsLeft -= 1
Loop
CompareScan = True
PerformScan()
Loop
End Sub
The thread is simply called by declaring a new thread.thread, however, I have created a class and a variable that the thread uses to check if it should still be running or not (similarly to how a backgroundworker would) this is illustrated by the "DLScanIntervalControl.CancelPending"
The form is then closed later by
Form.Close()
It can be reopened if the user clicks on a label that then uses the same method as shown above (FormCTRL.Show())
From MSDN:
"If the control's handle does not yet exist, InvokeRequired searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, the InvokeRequired method returns false."
In other words, you need to verify that the handle is created and then check if invoke is required.
If(Me.IsHandleCreated) Then
If(Me.InvokeRequired) Then
'...
Else
'...
End If
End If
I had a similar error when trying to use delegates to update controls on a form in another thread. I found that the handle is only created when it's "needed". I'm not sure what constitutes "needed", but you can force it to create the handle by accessing the Handle property of the object.
What I had done in my application is this:
' Iterate through each control on the form, and if the handle isn't created yet, call the
' Handle property to force it to be created
For Each ctrl As Control In Me.Controls
While Not ctrl.IsHandleCreated
Dim tmp = ctrl.Handle
tmp = Nothing
End While ' Not ctrl.IsHandleCreated
Next ' ctrl As Control In Me.Controls
It's rather ghetto, but it may help you here (If you still need the help)
I think the issue here has nothing to do with invoking, but references. Following this pseudocode...
Dim A As New Form1
A.Show()
''Spawn background thread with a reference to A
A.Dispose()
Dim B As New Form1
B.Show()
The thread is attempting to refer to the first instance of Form1 above which is disposed and will always stay that way.
If you want the thread to be able to update any form then you need to give the thread a (synchronised) way to refer to the form...
Public Class Worker
Private Target As Form1
Private TargetLock As New Object
Public Sub SetTargetForm(Frm as Form1)
SyncLock TargetLock
Target = Frm
End SyncLock
End Sub
Public Sub DoWork() ''The worker thread method
''Do work as usual then...
SyncLock TargetLock
If Target IsNot Nothing AndAlso Target.IsHandleCreated Then
If Target.InvokeRequired
Target.Invoke(...)
Else
...
End If
End If
End SyncLock
End Sub
End Class
This way, when a new form is available, you can inform the worker thread using SetTargetForm() and it will update the appropriate one.
Of course, you'd be better off refactoring the "Update UI" checks and invoke calls into a different method for simplicity/maintainability but you get the point.
Note that I haven't got an IDE to hand so there may be typos.
One final point... I'd question the value of disposing a form for memory management purposes. Controls are fairly lightweight in terms of memory and it's far more common for an object used by the form to be a memory hog than the form itself. Are you sure you're getting a real benefit for this added complexity?
For some reason a background thread in my app can't change any labels, textbox values, etc on my main form. There is no compile errors, when the thread executes nothing happens.
Here is some example code:
Imports System.Threading
Public Class Class1
Dim tmpThread As System.Threading.Thread
Private Sub bgFindThread()
Form1.lblStatus.Text = "test"
End Sub
Public Sub ThreadAction(ByVal Action As String)
If Action = "Start" Then
tmpThread = New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf bgFindThread))
tmpThread.Start()
ElseIf Action = "Abort" Then
If tmpThread.IsAlive = True Then tmpThread.Abort()
End If
End Sub
End Class
Can someone let me know what I'm doing wrong?
AFAIK code above will throw an exception IllegalCrossThreadException, it is because the background thread is not the same as UI thread and background try to set value on other thread. So windows form check every thread that work properly.
You can set Control.CheckForIllegalCrossThreadCalls to false to make it works.
Code below is when setting property is not run
Add into your code
------------------------------
Delegate Sub MyDelegate()
Private Sub RunMyControl()
lblStatus.Text = "test"
End Sub
Change your code
------------------------------
Private Sub bgFindThread
lblStatus.BeginInvoke (New MyDelegate(AddressOf RunMyControl))
End Sub
The method asyncronsly run code from background thread to UI thread.
You can only access UI controls from the UI thread.
I suggest reading this first: http://www.albahari.com/threading/
As others have mentioned, it is forbidden (for good reasons) to update UI elements from a non-UI thread.
The canonical solution is as follows:
Test whether you are outside the UI thread
If so, request for an operation to be performed inside the UI thread
[Inside the UI thread] Update the control.
In your case:
Private Sub bgFindThread()
If lblStatus.InvokeRequired Then
lblStatus.Invoke(New Action(AddressOf bgFindThread))
Return
End If
lblStatus.Text = "test"
End Sub
The only thing that changed is the guard clause at the beginning of the method which test whether we’re inside the UI thread and, if not, requests an execution in the UI thread and returns.
You can use a delegate to update UI controls in a background thread.
Example
Private Delegate Sub bkgChangeControl(ByVal bSucceed As Boolean)
Private dlgChangeControl As bkgChangeControl = AddressOf ChangeControl
Private Sub threadWorker_ChangeControl(ByVal bSucceed As Boolean)
Me.Invoke(dlgChangeControl, New Object() {bSucceed})
End Sub
Private Sub ChangeControl()
Me.lable="Changed"
End Sub
'In your background thread, call threadWorker_ChangeControl.