I currently have a program with 4 threads.
4 Threads are "Worker Threads" that each have a dedicated serial port that monitors a dedicated device. So Worker Thread 1 monitors Com port 1, Thread 2 monitors Com Port 2 etc.
All this all working fine. No conflicts.
However, the 4 worker threads all have to send commands to a 5th Comm Port as well, which is a communication link to a device that can powercycle the other devices.
I.E. they all have to share a specific resource, the 5th com port.
When they do send a command to this 5th shared each thread has to wait until the command has finished before continuing.
I've followed the coding example from Dan (thanks!) and tried to form a prototype test code.
This SEEMS to work.
I would appreciate a critical review of the code to see if I'm going in the right direction.
Apologies if I'm not explaining this very well as whilst I've used threads before. Handling a shared resource is new to me. Also I'm just getting to grips with how Stackoverflow works!!
Many thanks
A simplified solution using a shared instance of a resource and a lock.
Public Class Resource
Public Function Read() As String
Return "result"
End Function
End Class
Public Class ResourceUser
Private Shared resourceLock As New Object
Private Shared r As New Resource()
Public Function Read()
Dim res As String
SyncLock resourceLock
res = r.Read()
End SyncLock
Return res
End Function
End Class
Example usage:
Sub Main()
Dim t1 As New Threading.Thread(AddressOf DoSomethingWithResourceUser)
Dim t2 As New Threading.Thread(AddressOf DoSomethingWithResourceUser)
t1.Start()
t2.Start()
End Sub
Private Sub DoSomethingWithResourceUser()
Dim ru As New ResourceUser()
ru.Read()
End Sub
Here's another example more specific to serial comms. It uses a dictionary to keep track of physical comm resources and their relevant locks, so that you can do asynchronous access to different comm ports, but synchronize access to each single comm port.
Sub Main()
Dim c1 As New CommPortThreadSafe("COM1")
Dim c2 As New CommPortThreadSafe("COM2")
Dim c3 As New CommPortThreadSafe("COM1")
Dim t1 As New Threading.Thread(Sub() c1.Read())
Dim t2 As New Threading.Thread(Sub() c2.Read())
Dim t3 As New Threading.Thread(Sub() c3.Read())
' t1 and t3 can't be in critical region at same time
' t2 will be able to run through critical region
t1.Start()
t2.Start()
t3.Start()
End Sub
Public Class CommPort
Public Property Name As String
Public Function Read() As String
Return "result"
End Function
End Class
Public Class CommPortThreadSafe
Private Shared resourceLocks As New Dictionary(Of String, Object)()
Private Shared comms As New Dictionary(Of String, CommPort)()
Private Shared collectionLock As New Object()
Private commPortName As String
' constructor takes the comm port name
' so the appropriate dictionaries can be set up
Public Sub New(commPortName As String)
SyncLock collectionLock
Me.commPortName = commPortName
If Not comms.ContainsKey(commPortName) Then
Dim c As New CommPort()
Dim o As New Object()
c.Name = commPortName
' configure comm port further etc.
comms.Add(commPortName, c)
resourceLocks.Add(commPortName, o)
End If
End SyncLock
End Sub
Public Function Read()
Dim res As String
SyncLock resourceLocks(Me.commPortName)
res = comms(Me.commPortName).Read()
End SyncLock
Return res
End Function
End Class
To address your recent edits:
Threads A would all declare the comm port in the same way. Actually this is a benefit of this pattern (similar to multiton pattern) which works like a singleton when only one comm port is used. This code could be used in all threads:
Dim myCommPort As New CommPortThreadSafe("COM1")
The lock inside the read is going to synchronize access to COM1 because "COM1" (the name of the comm port) is actually the key to the Dictionary<string, object> used for locking. So when any thread reaches this code, keying with the same key, that region will be accessible only to a single thread because they all use the same key.
SyncLock resourceLocks(Me.commPortName)
res = comms(Me.commPortName).Read()
End SyncLock
As you saw, that string is set up in the constructor so as long as all the threads create their object passing the same string to the constructor, they will all have underlying indirect references to the same CommPort. The constructor can only create the instance if the name doesn't already exist in its dictionary:
If Not comms.ContainsKey(commPortName) Then
Dim c As New CommPort()
Here's another example usage with just one comm port:
Sub Main()
Dim ts As New ThreadStart(
Sub()
Dim c As New CommPortThreadSafe("COM1")
For i As Integer = 0 To 99
c.Read()
Next
End Sub)
Dim t1 As New Threading.Thread(ts)
Dim t2 As New Threading.Thread(ts)
Dim t3 As New Threading.Thread(ts)
Dim t4 As New Threading.Thread(ts)
t1.Start()
t2.Start()
t3.Start()
t4.Start()
End Sub
In this example we start 4 threads which each perform the code in the threadstart. There is a loop reading the comm port. If you test this out, you will see that it is thread-safe as long as the entire read takes place inside Read(), which you will need to develop of course. You may have another layer in which you are sending custom commands and waiting for a response. These two actions should both be inside a single SyncLock in each custom function. Thread B should use the same class if it's doing a similar thing.
FORM CODE FOR TESTING
Imports System.Threading
Public Class Form1
Private Worker(4) As jWorker '4 Worker Object
Public myWorkerThread(4) As Threading.Thread '4 runtime threads
Private Checker As jChecker '1 Checking Object
Public myCheckerThread As Threading.Thread ' Thread to check status of Resource
Dim MainThreadResouce As jResourceUser
'Assume the actual serial port is opened here
'so its available for access by the jResource Object.
'Pressing button 1 will start up all the threads
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'setup a another ResourceUser object here so can see one of the shared values from the form.
MainThreadResouce = New jResourceUser
'Setup and start worker threads - basically these do work and regularly
'send commands to a shared resource
For I = 1 To 4
Worker(I) = New jWorker
Worker(I).id = I
myWorkerThread(I) = New Threading.Thread(AddressOf Worker(I).doWork)
myWorkerThread(I).Start()
Next I
'Start Checking thread - regularly checks something in the resource
'please ignore this for now!
Checker = New jChecker
myCheckerThread = New Threading.Thread(AddressOf Checker.dochecking)
myCheckerThread.Start()
End Sub
End Class
========================
WORKER THREAD, MODELLING THE MONITORING THREADS
Imports System.Threading
Public Class jWorker
Private _id As Integer = 0
Private _workCount As Integer = 0
Private resourceUser As jResourceUser
Public workerStatus As String = ""
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
Me._id = Value
End Set
End Property
Public ReadOnly Property count() As Integer
Get
Return _workCount
End Get
End Property
Public Sub New()
resourceUser = New jResourceUser
End Sub
Sub doWork()
workerStatus = "Started"
Do Until False
Thread.Sleep(1000)
_workCount += 1
If _workCount Mod 5 = 0 Then
'THe line below would cause a bottleneck
'As it prevents the system trying to powercycle any other
'modems whilst its doing just one.
'resourceUser.PowerCycle(_id)
Debug.Print("W" & _id & ", Worker - Requesting Power OFF +++++++++++++")
resourceUser.Poweroff(_id)
Debug.Print("W" & _id & ", Worker - Waiting 10 secs for modem to settle")
Thread.Sleep(10000)
Debug.Print("W" & _id & ", Worker - Requesting Power ON")
resourceUser.PowerOn(_id)
Debug.Print("W" & _id & ", Worker - Finished Power cycle ------------")
End If
Loop
End Sub
End Class
===================
RESOURCE USER - TO CONTROL ACCESS TO SHARED RESOURCE
Public Class jResourceUser
'Variables for handling resouce locking
Private Shared resourceLock As New Object
Private Shared _resource As New jResource("Com1", "ON")
'keeps a status of which workers have signalled an OFF or ON via the Resource
'in the form of a string 11213141 (device number (1-4) - 1 for on, 0 for off
Private Shared _powerStatus As String
Public ReadOnly Property PowerStatus As String
Get
Return _powerStatus
End Get
End Property
Public Sub New()
_powerStatus = _resource.PowerState
End Sub
Sub PowerOn(ByVal WorkerID As Integer)
Debug.Print("W" & WorkerID & ", ResouceUser - requesting Lock for Power ON [" & _powerStatus & "]")
SyncLock resourceLock
_resource.TurnOn(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResouceUser - Turned On, Device statuses " & _powerStatus & "]")
End SyncLock
End Sub
Sub Poweroff(ByVal WorkerID As Integer)
Debug.Print("W" & WorkerID & ", ResouceUser requesting Lock for Power OFF [" & _powerStatus & "]")
SyncLock resourceLock
_resource.TurnOff(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResouceUser - Turned Off, Device statuses [" & _powerStatus & "]")
End SyncLock
End Sub
'Not going to work as it blocks the whole system when it could be
'reseting other modems.
Sub PowerCycle(ByVal WorkerID As Integer)
SyncLock resourceLock
Debug.Print("W" & WorkerID & ", ResourceUser - Requesting Power CYcle LockPower")
_resource.PowerCycle(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResourceUser - Power Cycled")
End SyncLock
End Sub
Function CheckState() As String
SyncLock resourceLock
Return _resource.CheckState
_powerStatus = _resource.PowerState
End SyncLock
End Function
End Class
============
RESOURCE - FOR ACTUAL WORK ON SHARED RESOURCE
'THis code would directly handle interactions
'with one specific com port that has already
'been configured and opened on the main thread.
Public Class jResource
Private _ComPort As String
Private _state As String
Private _PowerState As String
Private _CheckState As String
'Record the com port used for this resource
Public Property ComPort() As String
Get
Return _ComPort
End Get
Set(ByVal Value As String)
Me._ComPort = Value
End Set
End Property
'Returns the a particular status of the resouce
Public ReadOnly Property CheckState As String
Get
'here I'd send a few command to the comm port
'pick u the response and return it
Return _state
End Get
End Property
'The connected serial port is used to power cycle serveral devices
'this property returns the state of all those devices.
Public ReadOnly Property PowerState() As String
Get
Return _PowerState
End Get
End Property
Public Sub New(ByVal name, ByRef state)
Me._ComPort = name
Me._state = state
Me._PowerState = "11213141"
Me._CheckState = "ON"
End Sub
'Simulate a off command sent by a worker
'via its resourceUser object
Public Sub TurnOn(ByVal intWorker As Integer)
'simulate some work with the com port
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(500)
Do Until Now > myTimeOut
Loop
'Set the status to show that Device is on.
_PowerState = _PowerState.Replace(intWorker & "0", intWorker & "1")
Debug.Print("W" & intWorker & ", Resource - issued TurnON, Device Statuses [" & _PowerState & "]")
End Sub
Public Sub TurnOff(ByVal intWorker As Integer)
'simulate some work
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(500)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "0")
Debug.Print("W" & intWorker & ", Resource - issued TurnOFF, Device Statuses [" & _PowerState & "]")
End Sub
Public Sub PowerCycle(ByVal intWorker As Integer)
Debug.Print("W" & intWorker & ", Resource - issued PowerCycle, Device Statuses [" & _PowerState & "]")
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "0")
Debug.Print("W" & intWorker & ", Resource - issued TurnOFF, Device Statuses [" & _PowerState & "]")
'simulate some work - takes a while for device to turn off
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(10000)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "1")
Debug.Print("W" & intWorker & ", Resource - issued TurnON, Device Statuses [" & _PowerState & "]")
'simulate some work
myTimeOut = Now.AddMilliseconds(10000)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
End Sub
End Class
Related
I am getting an error and crash on my app from an invoke with ThreadAbortException "Thread was being aborted"
how can I still call the function but not crash?
I am running Lightstream to stream data from a server using live stream into my vb.net app so the OnUpdate is what triggers the call
Public Sub OnUpdate(itemPos As Integer, itemName As String, update As IUpdateInfo) Implements IHandyTableListener.OnUpdate
Dim sResult As String = "Case: " & update.ItemPos.ToString & " - " & Environment.NewLine & update.ToString
Call Process_Data_Call(update.ItemPos.ToString, sResult)
End Sub
Private Delegate Sub Process_Data_Delegate(iItemPos As String, sResult As String)
Private Sub Process_Data_Call(iItemPos As String, sResult As String)
If Me.InvokeRequired Then
Me.Invoke(New Process_Data_Delegate(AddressOf Process_Data_Call), New String() {iItemPos, sResult})
Else
'functions go here
End If
End Sub
This message is displayed when running my windows service.
The [service name] service on local computer started and then stopped.
Some Services stop automatically if they are not in use by another services or programs.
I am not sure what is causing this error. Below is the code for my service. My code uses another class called MagentoSalesOrder. I ran this code as a console application first and it worked just fine. I believe this what is causing the error. When I comment out the lines that use that class my service runs fine for printing test to a file.
Imports MyFirstService.MagentoSalesOrder
Public Class MyFirstService
Dim WithEvents timer1 As New System.Timers.Timer
Protected Overrides Sub OnStart(ByVal args() As String)
timer1.Interval = 10000
timer1.Start()
WriteLog(Me.ServiceName & " has started ...")
End Sub
Protected Overrides Sub OnStop()
WriteLog(Me.ServiceName & " has stopped ...")
End Sub
Private Sub timer1_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles timer1.Elapsed
WriteLog(Me.ServiceName & " is running ...")
End Sub
Private Sub WriteLog(ByVal strMessage As String)
Dim strPath As String, file As System.IO.StreamWriter
Dim test As New MagentoSalesOrder()
strPath = AppDomain.CurrentDomain.BaseDirectory & "\MyService.log"
file = New System.IO.StreamWriter(strPath, True)
Dim arr = test.BuildPreOrder()
If (arr.Length > 0) Then
For Each element As Long In arr
file.WriteLine("PreOrder created: " + element)
Next
Else
file.WriteLine("No orders to process")
End If
'file.WriteLine("Test")
file.Close()
End Sub
End Class
So I found out my error was coming from the file.writeline in my foreach loop.
Changing element to element.toString in my writefile fixed my service.
The problem came from trying to concatenate a Long to a String.
Changing element to element.toString in my writefile fixed my service.
I'am coding a Multi-Threaded TCP echo server, but the problem is when i call UpdateMessage from ClientHandler class, the RichTextBox's text is not appending.
This is the Form1 class that containts the RichTextBox1
Public Class Form1
Const PORT As Integer = 1234 'The port number on which the server will listen for connection.
Dim ServerSocket As New TcpListener(PORT) 'The Server Socket that will listen for connections on specified port number.
Dim Link As TcpClient 'The Socket that will handle the client.
Dim NumberOfClients As Integer = 0 'The total number of clients connected to the server.
Dim myThread As Thread 'The thread on which the server will handle the client
Dim sc As SynchronizationContext = SynchronizationContext.Current
Public Clienthandler As ClientHandler
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles btnStart.Click
ServerSocket.Start()
' UpdateMessage("Server started..")
myThread = New Thread(AddressOf AcceptClients) 'Handle the client in a different thread
myThread.Start()
btnStart.Text = "Started!"
btnStart.Enabled = False
End Sub
Private Sub AcceptClients()
'Keep accepting and handling the clients.
While (True)
Link = ServerSocket.AcceptTcpClient() 'Accept the client and let the Socket deal with it.
NumberOfClients = NumberOfClients + 1 'update the total number of clients
Dim ClientAddress As String = Link.Client.RemoteEndPoint.ToString
' UpdateMessage("Client number " & NumberOfClients & " connected from " & ClientAddress)
Clienthandler = New ClientHandler
Clienthandler.StartClientThread(Link, NumberOfClients)
Me.sc.Post(AddressOf Clienthandler.UpdateMessage, "Client number " & NumberOfClients & " connected from " & ClientAddress)
End While
'close the sockets
Link.Close() 'close the Socket.
ServerSocket.Stop() 'Stop the Server Socket.
End Sub
End Class
And here is the ClientHandler class:
Public Class ClientHandler
Private Link As TcpClient
Private ClientNumber As Integer
Dim NumberOfMessages As Integer = 0 'The number of messages that client has sent.
Dim Stream As NetworkStream 'The stream being used for sending/receiving data over Socket.
Dim scHandler As SynchronizationContext = SynchronizationContext.Current
Public Sub StartClientThread(Link As TcpClient, ClientNumber As Integer)
'Initialize the class variables with the arguments passed in StartClient
Me.Link = Link
Me.ClientNumber = ClientNumber
'Start the thread
Dim ClientThread As New Thread(AddressOf Chat)
ClientThread.Start()
UpdateMessage("Thread for client " & ClientNumber & " started!")
End Sub
Public Sub UpdateMessage2(Message As String)
Me.scHandler.Post(AddressOf UpdateMessage2, Message)
End Sub
Public Sub UpdateMessage(Message As String)
Message = vbNewLine & Trim(Message)
If Message.Length < 1 Then
Message = "empty"
End If
MessageBox.Show("Updating message!" & vbNewLine & Message)
Form1.RichTextBox1.AppendText(Message)
End Sub
The same problem still exits, the RichTextBox is being updated from UI Thread only.
To be a bit more specific than Hans Passant, using the form name where an object is expected makes use of the default instance of the form, which is an instance that the system creates and manages for you. The default instance is thread-specific though, which means that there is one default instance per thread. If you use the default instance in a secondary thread then you are using a different object to the one that you displayed on the UI thread. You might read a bit more about default instances here:
http://jmcilhinney.blogspot.com.au/2009/07/vbnet-default-form-instances.html
You need to somehow marshal a method call back to the UI thread and then update the existing form instance on that thread. There are a couple of ways that can be done:
Have access to the existing form object and call its Invoke method.
Use the SynchronizationContext class to marshal the method call to the UI thread and then use the default instance.
Class myClass
Friend TB As TextBox
Class myThread
' ...
End Class
End Class
Class Form1
Overrides Sub OnLoad
_tcpClient = New myClass
Controls.Add(_tcpClient.TB)
_tcpClient.doSomething()
End Sub
End Class
like that, as per my comments.
I found this code on youtube and follow exactly like it. The MAC address and the nic name showed but the IPv4 wont show. Basically i want to show ipv4 address for all network interface inside my computer with the either its connected or not. Here the code
Private Sub getinterface()
'get all network interface available in system
Dim nics As NetworkInterface() = NetworkInterface.GetAllNetworkInterfaces()
If nics.Length < 0 Or nics Is Nothing Then
MsgBox("No network interfaces found")
Exit Sub
End If
'if interfaces are found let list them. first clear the listview items
ListView1.Items.Clear()
For Each netadapter As NetworkInterface In nics
'next lets set variable to get interface properties for later use
Dim intproperties As IPInterfaceProperties = netadapter.GetIPProperties()
'now add the network adaptername to the list
ListView1.Items.Add(netadapter.Name)
'now get the mac address of this interface
Dim paddress As PhysicalAddress = netadapter.GetPhysicalAddress()
Dim addbyte As Byte() = paddress.GetAddressBytes()
Dim macaddress As String = ""
'now loop through the bytes value and change it to hex
For i = 0 To addbyte.Length - 1
macaddress &= addbyte(i).ToString("X2") 'change string to hex
'now let separate hex value with -except last one
If i <> addbyte.Length - 1 Then
macaddress &= "-"
End If
Next
'ount item in listview
Dim icount As Integer = ListView1.Items.Count
'use try
Try
With ListView1.Items(icount - 1).SubItems
.Add(macaddress)
'.Add(intproperties.UnicastAddresses(2).Address.ToString)
.Add(intproperties.AnycastAddresses(2).Address.ToString)
.Add(intproperties.UnicastAddresses(2).IPv4Mask.ToString)
.Add(intproperties.UnicastAddresses(0).Address.ToString)
.Add(intproperties.UnicastAddresses(1).Address.ToString)
'.Add( IPAddress.Parse(a).AddressFamily == AddressFamily.InterNetwork )
End With
Catch ex As Exception
End Try
Next
'now lets make auto size columns
ListView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
getinterface()
End Sub
Is there a solution for this code or there is another simpler way to do this for display all nic name with its ipv4 address? currently using visual basic express 2010 express
Use WMI queries for this - it will be a lot simpler.
There are some useful classes available - Win32_NetworkAdapterConfiguration & Win32_NetworkAdapter for getting these details. For easy WMI code creation, search for WMI Code Creator
Imports System
Imports System.Management
Imports System.Windows.Forms
Namespace WMISample
Public Class MyWMIQuery
Public Overloads Shared Function Main() As Integer
Try
Dim searcher As New ManagementObjectSearcher( _
"root\CIMV2", _
"SELECT * FROM Win32_NetworkAdapterConfiguration")
For Each queryObj As ManagementObject in searcher.Get()
Console.WriteLine("-----------------------------------")
Console.WriteLine("Win32_NetworkAdapterConfiguration instance")
Console.WriteLine("-----------------------------------")
If queryObj("IPAddress") Is Nothing Then
Console.WriteLine("IPAddress: {0}", queryObj("IPAddress"))
Else
Dim arrIPAddress As String()
arrIPAddress = queryObj("IPAddress")
For Each arrValue As String In arrIPAddress
Console.WriteLine("IPAddress: {0}", arrValue)
Next
End If
Console.WriteLine("IPEnabled: {0}", queryObj("IPEnabled"))
Console.WriteLine("MACAddress: {0}", queryObj("MACAddress"))
Next
Catch err As ManagementException
MessageBox.Show("An error occurred while querying for WMI data: " & err.Message)
End Try
End Function
End Class
End Namespace
What I am trying to do is, creating an application which executes some action. there should be maximum of 10 threads running.
I have the following code, which works fine. I need to send a parameter to "Somework" procedure. How can I do that?
Module Module1
Sub Main()
Dim Task As New Action(AddressOf SomeWork)
dim I as integer
for i=1 to 20
If RunningThread < 10 Then
Task.BeginInvoke(AddressOf Callback, Nothing)
Threading.Interlocked.Increment(RunningThread)
Else
SyncLock (Lock)
tasks.Enqueue(Task)
End SyncLock
End If
next
Console.ReadLine()
End Sub
Private tasks As New Queue(Of action)
Private RunningThread As Integer
Private Lock As New Object
Dim I As Integer = 0
Private Sub SomeWork()
I += 1
Console.WriteLine(I & " doing some work - begin :: " & Now.ToString)
Threading.Thread.Sleep(10000)
Console.WriteLine(I & " doing some work - end :: " & Now.ToString)
End Sub
Private Sub Callback(ByVal o As Object)
If tasks.Count > 0 Then
Dim Task As Action
SyncLock (Lock)
Task = tasks.Dequeue
End SyncLock
Task.BeginInvoke(AddressOf Callback, Nothing)
Else
Threading.Interlocked.Decrement(RunningThread)
End If
End Sub
End Module
Kindly help.
Thanks
You can achieve your requirements easily using the Task Parallel Library (TPL) using Parallel.ForEach. Use a constructor that allows you to specify a ParallelOptions parameter and set the MaxDegreeOfParallelism to your thread limit.