|
-
Sep 30th, 2016, 08:36 PM
#2
Re: Continuous data update on a background worker thread?
I do this kind of thing all the time.
A background worker is really like you said, for performing a task in the background that will usually finish up and return a result.
Instead of a background worker, the pattern I've used over and over multiple times is to have a background thread who's sole responsibility is to receive data as it comes in and depending on how many different types of messages may come in and how complex they are to parse, usually have a class associated with each message type that the raw data can be sent to a parse method.
If I have multiple sources of data coming from different machines, then I would have a background thread for each receiver to process messages from those machines (usually).
I have a module in each project lately, called SharedData, that holds all the data that needs to be displayed on the various "pages" the user can select and the data they select to be displayed.
I do have a timer (called guiTimer) that I run at a reasonable rate like 100 ms to update the current pages displayed independent of the speed the data is received. I typically receive and process a few hundred messages per second, but have run some number of thousand messages per second.
Some example code, I guess I can strip out of one of the projects.
There isn't that much to creating a thread that runs in the background.
Just need to be aware it can't directly access the GUI (which you don't want anyway), but it can write to the public memory.
To access the GUI from a background thread, if needed, is done by invoking a delegate that will run on the GUI thread to complete the action.
All .Net intrinsic types (byte, Integer, Single, etc..) are immutable so you don't have to worry about one thread writing and another thread reading from the variable with one thread getting a half modified value. But, for your object types, i.e. instances of classes you might create and Lists and Dictionaries etc., you do need to implement access controls or use concurrent versions which have the access controls built-in.
Ok, here is the Load method from the main form (this was an adjunct program, so the form wasn't renamed to something meaningful), and I stripped out some stuff which is not important to the design.
Code:
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
TerrainHeightInterface.Init()
VehicleMsgReceiver.Init()
guiTimer.Interval = 50
guiTimer.Start()
End Sub
So it looks like in this case, I update the GUI at around 20hz.
Since I usually only need one instance of a particular class to run a background thread and receive data, I keep it simple by using a Static Class (although VB doesn't provide a Static Class type, other than the module) you achieve the same result by taking all the public functions Shared and public data Shared, and you don't need to create an instance of the class, just use those methods and access the data.
I don't bother creating properties in the classes for the majority of this type of program.
The Load event handler is calling two Shared Public methods (TerrainHeightInterface.Init() and VehicleMsgReceiver.Init() ) in those two classes to kick the receivers off in background threads.
Let's look at the simpler one to see what it looks like.
Code:
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Public Class VehicleMsgReceiver
'Not instantiated. All access through the shared methods and properties
Private Shared SimControlEndPoint As New IPEndPoint(IPAddress.Parse("127.0.0.1"), 12330)
Private Shared socketIn As New UdpClient(12341) 'Listen on UDP port 12421
Private Shared destIPnt As IPEndPoint
Private Shared receiveThread As New Thread(AddressOf Receiver)
Public Shared Sub Init()
receiveThread.IsBackground = True
receiveThread.Start()
End Sub
Private Shared Sub Receiver()
Dim remoteIP As New IPEndPoint(IPAddress.Any, 0) 'will hold who sent the data
Dim msg() As Byte 'buffer to receive data
Dim pktID As Int32
Dim vehID As Int32
Dim RunState As Int32
Dim SimTOD As Int32
Do Until leaving
remoteIP = New IPEndPoint(IPAddress.Any, 0)
Try
msg = socketIn.Receive(remoteIP)
pktID = msg(0)
' ===============================================
If pktID = TIMETICK_MSG Then
'===============================================
' parse the message, writting the values out to SharedData variables
'===============================================
ElseIf pktID = COMMAND_MSG Then
'===============================================
'
'Forward the message on to another process
'If the message is an update Then
' Call a parser to do a local update
'End if
'===============================================
ElseIf pktID = etc... Then
'===============================================
End If 'we got a hearbeat message, et al
Catch ex As Exception
If Complain Then
MsgBox(ex.Message)
End If
End Try
Loop
End Sub
Public Shared Sub RemoveVehicle(id As Int32)
SyncLock VehListLock
VehicleList.Remove(id)
BuildIndexCrossReferences()
End SyncLock
SyncLock TelemetryListLock
TelemetryList.Remove(id)
End SyncLock
If SelectedVehicle = id Then
SelectedVehicle = -1 'SelectedVehicle no longer valid
End If
End Sub
End Class
I removed the majority of the code.
But the .Init method that was called from the Form Load event is all there.
Let's repeat it here, plus the declaration of the thread.
Code:
Private Shared receiveThread As New Thread(AddressOf Receiver)
Public Shared Sub Init()
receiveThread.IsBackground = True
receiveThread.Start()
End Sub
You just need to declare the variable of type thread, and point it at what code you want to run in the thread.
Then set it to background (so it will exit when the main (i.e. GUI) thread exits.
Then start it up, and it runs in the background until you choose to exit (I have the Leaving boolean it is looping on), or the application exits, which will stop all background threads. (If it wasn't a background thread it could be left running
even after the Form closes).
That is pretty simple and I really like it a lot and use background threads for interface processing all the time.
I shouldn't need to post the SharedData Module. It is just a Module named SharedData, and has all the public variables that need to be accessed by code in various parts.
Which reminds me, I left a stripped down version of a sub (RemoveVehicle) in the above example so you can see the synchronization method I generally use (synclock) in action to guard access to objects from multiple threads.
Those synclocks objects I declared in SharedData, so I might as well show some of that module after all.
Code:
Module SharedData
Public leaving As Boolean
Public FltMdlInterface As New FlightModelsInterface
Public SimElapsedTime As Integer 'in millisecond
Public AcceleratedTime As Integer = 1
Public SimTimeOfDay As TimeSpan
Public TimeTickAge As Integer
Public VehicleList As New Dictionary(Of Int32, Vehicle) 'Vehicle ID, Vehicle Class
Public TelemetryList As New Dictionary(Of Int32, Telemetry) 'Vehicle ID, Telemetry
Public Complain As Boolean = False
Public VehListLock As New Object 'To lock the vehicle list for single thread access
Public TelemetryListLock As New Object 'To lock the telemtry list for access
End Module
Last edited by passel; Sep 30th, 2016 at 08:45 PM.
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
|