I'm new to the world of threading, but a few aspects of an app I'm working on require me to use a BackgroundWorker control to prevent the UI freezing up while it's doing some file operations.
What I'm trying to do is update a couple of form labels from within the BackgroundWorker. Having never worked with this before I very quickly discovered that I can't access controls that weren't created within the same thread, so after a bit of research I've implemented the following code that seems to make everything work:
Private Delegate Sub DelegateUpdateStatus(ByVal statusText As String, ByRef currentFile As String)
Private Sub UpdateStatus(ByVal statusText As String, ByVal currentFile As String)
If InvokeRequired Then
Invoke(Sub() LblStatus.Text = statusText)
Invoke(Sub() LblCurrentFile.Text = currentFile)
Else
LblStatus.Text = statusText
LblCurrentFile.Text = currentFile
End If
End Sub
Thing is though, I have no understanding of what this code is doing, or why it's required.
I've done a bit of research but I've not done any real work with this kind of thing, and most articles I've read assume some kind of prior knowledge.
The three main things I looking to gain an understanding of:
- Why this code is required (as in, why the controls can't be accessed directly from the BackgroundWorker)
- What a delegate is, and when the use of it is required
- What the Invoke method does, and what I'm checking for with InvokeRequired
As said, threading is still quite a foreign concept, so any answers in plain English would be really helpful - thanks!
EDIT: Thanks for the responses so far everybody. I've done some further reading, and I'm wondering if I'm going about this the right way. The reason I'm using a BackgroundWorker is to ensure that the UI remains responsive while I'm performing file operations. The issue is, I still need to wait until the BackgroundWorker has done its job so I can return a boolean indicating the success of the operation. There's ways to work around this, but from my reading, having to wait for a BackgroundWorker to complete its work is defeating the purpose of using it in the first place. So, what's the best way to go about preventing the UI from locking up?
OK, well done for getting so far.
Lets start with point 1.
From How to: Make Thread-Safe Calls to Windows Forms Controls
If you use multithreading to improve the performance of your Windows
Forms applications, you must make sure that you make calls to your
controls in a thread-safe way.
Access to Windows Forms controls is not inherently thread safe. If you
have two or more threads manipulating the state of a control, it is
possible to force the control into an inconsistent state.
So, as you can see, you need to ensure that when changing the state of a control, it is done in a threadsafe manner.
Now, the property Control.InvokeRequired checks whether the code you are executing is on a different thread to the one that originally created the control.
If it is, we need some way to call code on that original thread.
For this reason you need to use Control.Invoke Method to execute such code on the original thread.
Executes a delegate on the thread that owns the control's underlying
window handle.
Now the thing is, you need to tell that thread what it should be executing, and this is done using the delegate.
Represents a delegate, which is a data structure that refers to a
static method or to a class instance and an instance method of that
class.
Now, the last thing I would recommend is that you look at what the differences between Delegates, Anonymous Delegates, Anonymous Methods and Lamda expressions.
InvokeRequired
asks 'Am I on the right thread?', if so carry on, else I need a delegate - in your code you see a lambda Sub
Invoke(Sub() LblStatus.Text = statusText)
The other way this could work is to direct the result to a diff Sub routine(where the delegate has crossed over to the correct thread), but here we are able to run a Sub inside the Invoke method itself - just a simpler way to do it.
A delegate is required any time you use a separate thread to do work async.
Invoke - MSDN
BackgroundWorker is another way to have thread and make thread-safe call from it.
on ProgressChanged
, and RunWorkerCompleted
you can make thread-safe calls to UI
Delegate is just reference to the method
Control.Invoke(Delegate)
Executes the specified delegate on the thread that owns the control's
underlying window handle.
Control.InvokeRequired
Gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread
than the one the control was created on.
And Why we need it all these stuffs .
From msdn document it is clarified :
If you have two or more threads manipulating the state of a control,
it is possible to force the control into an inconsistent state.
Other thread-related bugs are possible, such as race conditions and
deadlocks. It is important to make sure that access to your controls
is performed in a thread-safe way.
So for safety reasons we have to implement this, otherwise our worker
threads and UI thread may both try to access the data member at once,
and it will wreak havoc on our application . So instead of disabling
it and having a chance crash possibility of your application, you
should better use thread-safe patterns .