2014-09-02 1 views
4

В настоящее время у меня есть программа с 4 потоками.Как разделить ресурс (последовательный порт) с несколькими потоками

4 Темы - это «Рабочие потоки», каждая из которых имеет выделенный последовательный порт, который контролирует выделенное устройство. Так что Worker Thread 1 контролирует Com port 1, Thread 2 мониторы Com Port 2 и т. Д.

Все это работает нормально. Никаких конфликтов.

Однако 4 рабочих потока должны также отправлять команды на 5-й порт Comm, который является линией связи с устройством, которое может приводить в действие другие устройства.
I.E. все они должны совместно использовать определенный ресурс, 5-й COM-порт.

Когда они отправляют команду на этот 5-й общий доступ, каждый поток должен дождаться завершения команды до продолжения.

Я следовал примеру кодирования от Дэна (спасибо!) И попытался сформировать тестовый код прототипа. Это СМОТРЕТЬ работать.

Я был бы признателен за критический обзор кода, чтобы узнать, не пойду ли я в правильном направлении.

Извинения, если я не очень хорошо объясняю это, пока я использовал темы раньше. Работа с общим ресурсом для меня является новой. Также я просто понимаю, как работает Stackoverflow!

Большое спасибо

+1

для столкновений, почему у вас нет одного класса и доступа к последовательному порту оттуда (таким образом, он только получает открыт один раз,). Кроме того, как насчет добавления буфера и записи ваших данных в следующее свободное место в очереди? – jbutler483

+0

что-то подобное на самом деле предлагается здесь: http: //stackoverflow.com/a/4043762/3436942 – jbutler483

+0

Вы имеете в виду каждую тему, называющую этот класс? Не было бы это потокобезопасным, потому что класс все еще вызывает общий ресурс. Например, один поток может вызвать класс для записи на последовательный порт, в то время как другой может уже считывать его. Или вы хотите, чтобы класс открыл порт и принял сообщения из других потоков, помещая их в очередь действий? Если да, то каким образом я могу поделиться классом между потоками? – user3844416

ответ

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 
+0

А, похоже, мне НУЖНО делать некоторые чтения на SyncLock. – user3844416

+0

Итак, если я правильно соблюдаю, я мог бы каждый поток создавать экземпляр объекта ResourceUser при запуске (так как потоки должны иметь регулярную связь с последовательным портом), тогда, когда поток хочет использовать последовательный порт, он просто вызывает ResourceUser.WhatEverFUnctionIsNeeded ? И объект ResourceUser, затем управляет связанным вызовом к WhatEverFUnctionIsNeeded в объекте, который обрабатывает последовательный порт? – user3844416

+0

Причина того, что поток, использующий другой класс для обработки класса последовательного порта, является таким образом, что класс ResouceUser может обрабатывать блокировку? – user3844416

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 

Для решения ваших последних изменений:

Темы А будет все объявить порт комм таким же образом. На самом деле это преимущество этого шаблона (похожего на многотонный рисунок), который работает как одноэлементный, когда используется только один коммуникационный порт. Этот код может быть использован во всех потоках:

Dim myCommPort As New CommPortThreadSafe("COM1") 

замок внутри чтения будет синхронизировать доступ к COM1, потому что «COM1» (название порта комм) на самом деле ключ к 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 темы каждый из которых выполняет код в начале строки. Существует петля, считывающая коммуникационный порт. Если вы проверите это, вы увидите, что он является потокобезопасным, если все чтение происходит внутри Read(), которое вам нужно будет развивать, конечно. У вас может быть другой уровень, в котором вы отправляете пользовательские команды и ожидаете ответа. Эти два действия должны быть внутри одного SyncLock в каждой настраиваемой функции. Thread B должен использовать тот же класс, если он делает аналогичную вещь.

+0

Спасибо Dan. Мне нужно потратить некоторое время на это. Cheers – user3844416

+0

Привет, Dan. Еще раз спасибо за это. Тем не менее, это меня немного сбивает с толку, потому что я беспокоюсь только об одном порте Comm. Я отредактировал вопрос, надеюсь, объясню немного больше. Спасибо за вашу помощь. – user3844416

+0

@ user3844416 отредактировал мой ответ – djv

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 

=================== РЕСУРС USER - для управления доступом к совместно используемому ресурсу

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 

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

RE ИСТОЧНИК - ДЛЯ АКТУАЛЬНОЙ РАБОТЫ ПО ОБЩЕГО РЕСУРСУ

'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 
+0

На самом деле, если Дэн помог вам, вы должны принять его ответ и опубликовать код, который вы хотите просмотреть в ['CodeReview'] (http://codereview.stackexchange.com/). На самом деле это не место, где можно критически взглянуть на ваш код. Одна вещь, которую я заметил, это то, что в вашем методе 'CheckState' класса' jResourceUser' вы возвращаете 'resource.CheckState' перед назначением' _powerStatus', поэтому '_powerStatus = _resource.PowerState' недоступен –

+0

Ok Спасибо. Я не знал о CodeReview, но звучит полезно. – user3844416

Смежные вопросы