如何与多个线程共享资源(串行端口)

时间:2014-09-02 12:12:04

标签: vb.net multithreading

我目前有一个包含4个线程的程序。

4个线程是“工作线程”,每个线程都有一个监视专用设备的专用串行端口。因此,工作线程1监视Com端口1,线程2监视Com端口2等。

这一切都很好。没有冲突。

但是,4个工作线程都必须向第5个Comm端口发送命令,这是指向可以为其他设备供电的设备的通信链接。
I.E.他们都必须共享一个特定的资源,第五个com端口。

当他们向第5个共享发送命令时,每个线程必须等到命令完成后再继续。

我遵循Dan的编码示例(谢谢!)并尝试形成原型测试代码。 这个SEEMS可以工作。

我很感激对代码的严格审查,看看我是否正朝着正确的方向前进。

道歉,如果我之前没有像以前那样使用线程那么解释得那么好。处理共享资源对我来说是新的。此外,我只是想了解Stackoverflow的工作原理!!

非常感谢

3 个答案:

答案 0 :(得分:1)

使用资源和锁的共享实例的简化解决方案。

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

使用示例:

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

答案 1 :(得分:1)

这是另一个更具体的串行通信示例。它使用字典来跟踪物理通信资源及其相关锁,以便您可以对不同的通信端口进行异步访问,但同步访问每个通信端口。

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

解决您最近的修改:

线程A将以相同的方式声明通信端口。实际上,这是这种模式的一个好处(类似于多重模式),当只使用一个通信端口时,它就像一个单独的模式。此代码可用于所有线程:

Dim myCommPort As New CommPortThreadSafe("COM1")

读取内部的锁定将同步对COM1的访问,因为&#34; COM1&#34; (通信端口的名称)实际上是用于锁定的Dictionary<string, object>的关键。因此,当任何线程到达此代码时,使用相同的键进行键控时,该区域只能由单个线程访问,因为它们都使用相同的键。

SyncLock resourceLocks(Me.commPortName)
    res = comms(Me.commPortName).Read()
End SyncLock

如您所见,该字符串是在构造函数中设置的,因此只要所有线程创建的对象将相同的字符串传递给构造函数,它们都将具有对同一CommPort的基础间接引用。如果名称在其字典中已经存在,则构造函数只能创建实例:

 If Not comms.ContainsKey(commPortName) Then
     Dim c As New CommPort()

这是另一个只使用一个通信端口的示例用法:

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

在这个例子中,我们启动4个线程,每个线程执行threadstart中的代码。有一个循环读取通讯端口。如果您对此进行测试,只要整个读取发生在Read()内,您将看到它是线程安全的,当然您需要开发它。您可能有另一个图层,您在其中发送自定义命令并等待响应。这两个操作都应该位于每个自定义函数中的单个SyncLock中。如果线程B执行类似的操作,它应该使用相同的类。

答案 2 :(得分:0)

表格测试代码

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

======================== 工人线,模拟监测线

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

=================== 资源用户 - 控制对共享资源的访问

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

============

资源 - 关于共享资源的实际工作

'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