|
-
Feb 17th, 2016, 10:28 AM
#4
Re: Struggling with Form multithreading
It's an easy topic, but a hard one. What I mean is, the concepts you need to know are pretty simple, but there's a fairly wide array of tricks you have to use to accomplish it. That's why both jay20aiii and dbasnett are "right", but their examples look completely different.
Here's the basic concept:
The first important thing to know is everything you do "belongs" to some thread. Sometimes you care about that.
When your application starts up, the UI is created and set up on a special thread we call either "the UI thread" or "the main thread". This thread is special because VB sets up some things needed for a UI application on it. If anything that tries to work with a control belongs to a different thread, the result is unpredictable and can range from "nothing happens" to "the application crashes".
So there are special techniques that let you say, "Make sure this code executes on the UI thread." Which technique you use depends a lot on how you came to be on another thread in the first place, or if you're using Windows Forms vs. WPF, and several other factors. So you have to know a few tricks. There are ways to design your forms and other classes to help mitigate this.
Show some code!
I can give very specific advice if I can see your code and how you end up creating worker threads. There's at least 5 ways to schedule work on other threads in .NET, and some of them are a lot easier than others. For example, here's an example using VS 2013 and later's Task Asynchronous Pattern with Async/Await:
Code:
Private Async Sub LoadData()
Dim fileParser As New yourFileParser()
Dim customerData = Await fileParser.ParseAsync(...)
For Each entry in customerData
Dim result = Await customerData.ProcessAsync(...)
UpdateListView(result)
UpdateProgress()
Next
End Sub
The neat thing about this pattern is that "Await" keyword. It says, "I know this method is going to do things on a worker thread, and I want to do things when it finishes. So when you get here, start that thread, then leave this method so the form can do other things. When the thread is finished, come back here on this same thread and continue." It's extremely elegant and eliminates 90% of the worries of threading.
But this is what it'd look like with the Event-Based Asynchronous Pattern, which is how BackgroundWorker functions:
Code:
Private Sub LoadData()
Dim fileParser As New YourFileParser()
AddHandler fileParser.ParseCompleted, AddressOf HandleParseCompleted
fileParser.ParseAsync(...)
End Sub
Private Sub HandleParseCompleted(ByVal sender As Object, ByVal e As ParseCompletedEventArgs)
Dim customerData = e.Results
Dim dataProcessor As New CustomerDataProcessor(customerData)
AddHandler dataProcessor.Progress, AddressOf HandleDataProgress
AddHandler dataProcessor.ProcessCompleted, AddressOf HandleProcessCompleted
dataProcessor.ProcessAsync(customerData)
End Sub
Private Sub HandleDataProgress(ByVal sender As Object, ByVal e As DataProgressEventArgs)
Dim result = e.Result
UpdateListView(result)
UpdateProgress()
End Sub
Private Sub HandleProcessCompleted(ByVal sender As Object, ByVal e As ProcessCompletedEventArgs)
Finish()
End Sub
Obviously there's a lot more footwork there. In each of the event handlers, it's safe to do things to controls.
Now, if you're using Thread instances, or ThreadPool, or manipulating tasks without Async/Await, there's more tricks. Every control has a property named InvokeRequired that is safe to get from any thread. If the code that gets it is running on the "wrong" thread, it will return True.
Every control also has an Invoke() method, that takes a delegate. (Delegates are variables that can store method calls.) It will make sure the UI thread calls that delegate the next time it's not busy. You can combine this with InvokeRequired to add methods to your form that are safe to call from any thread. For example, here's one that updates a TextBox:
Code:
Public Sub UpdateStatus(ByVal statusMessage As String)
If txtStatus.InvokeRequired Then
txtStatus.Invoke(Sub() UpdateStatus(statusMessage))
Else
txtStatus.Text = statusMessage
End If
End Sub
That's safe to call from anywhere, because it checks if it's on the right thread before doing anything. This is a really common pattern because it's as old as .NET. (That doesn't mean it's bad.) In WPF, you do something slightly different with an object called the Dispatcher. In ASP .NET, I'm pretty sure there's a different mechanism. This isn't the first trick I reach for, but there are times when the other techniques can't do what you need so it's nice to know this one, too.
So, what do you need to do?
You need to go over all of your code and find the places where you use a control on the wrong thread. Then, you need to consider ways to make that access safe. Sometimes the answer is, "Ah, I can do this after the task completes." Other times it's, "No, I need to do this right now, I need to use Invoke()." I suggest taking it very slow, changing one thing at a time and making sure it works before moving to another. If you get stuck, post the code involved. Lots of us have spent lots of time handling these issues, it's one of the things I think gets the fastest and best answers on this forum.
It's also my hobby topic. I /love/ writing about async code.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|