.NET以有序的方式终止线程

时间:2009-07-26 17:31:13

标签: vb.net multithreading

目前,我有一个由生产者和消费者线程运行的RingBuffer。

在寻找一种有序终止它们的方法时,我想我会使用一个标志来指示生产者何时完成,然后在我的消费者中检查该标志以及需要写入的环形缓冲区槽的数量。如果生产者已经完成并且环形缓冲区没有需要写入的插槽,则消费者可以终止。

效果很好。

但是,如果我通过插入睡眠来人为地延长生产者所花费的时间,则消费者不会终止。我相信这是使用信号量的结果。

这是我正在使用的代码。请注意,在写入所有插槽后程序将“挂起”。生产者终止,但消费者“挂起”。

任何关于以有序方式终止的建议都将受到高度赞赏。

编辑 - 使用Henk建议使用队列更新了代码。 +1000指向第一个建议终止消费者/生产者线程的更好的方法知道正在使用的项目的确切数量返回值为null / nothing 表示队列中不再存在任何项目(尽管这并不意味着它们仍未生成。)

编辑 - 我相信我已经明白了。只需为每个使用者传递null或不传递给RingBuffer.Enqueue,并捕获使用者中的null或nothing对象以终止它。希望有人觉得这很有用。

Imports System.Collections

Module Module1

    Public Class RingBuffer

        Private m_Capacity As Integer
        Private m_Queue As Queue

        Public Sub New(ByVal Capacity As Integer)

            m_Capacity = Capacity
            m_Queue = Queue.Synchronized(New Queue(Capacity))

        End Sub

        Public Sub Enqueue(ByVal value As Object)

            SyncLock m_Queue.SyncRoot

                If m_Queue.Count = m_Capacity Then
                    Threading.Monitor.Wait(m_Queue.SyncRoot)
                End If

                m_Queue.Enqueue(value)

                Threading.Monitor.PulseAll(m_Queue.SyncRoot)

            End SyncLock

        End Sub

        Public Function Dequeue() As Object

            Dim value As Object = Nothing

            SyncLock m_Queue.SyncRoot

                If m_Queue.Count = 0 Then
                    Threading.Monitor.Wait(m_Queue.SyncRoot)
                End If

                value = m_Queue.Dequeue()

                Console.WriteLine("Full Slots: {0} - Open Slots: {1}", m_Queue.Count, m_Capacity - m_Queue.Count)

                Threading.Monitor.PulseAll(m_Queue.SyncRoot)

            End SyncLock

            Return value

        End Function

    End Class

    Public Class Tile

        Public buffer() As Byte

        Public Sub New()

            buffer = New Byte(1023) {}

        End Sub

    End Class

    Public Sub Producer(ByVal rb As RingBuffer)

        Dim enq As Integer = 0
        Dim rng As New System.Security.Cryptography.RNGCryptoServiceProvider

        For i As Integer = 0 To 1023
            Dim t As New Tile
            rng.GetNonZeroBytes(t.buffer)
            rb.Enqueue(t)
            enq += 1
            Threading.Thread.Sleep(10)
        Next i
        rb.Enqueue(Nothing)

        Console.WriteLine("Total items enqueued: " & enq.ToString())
        Console.WriteLine("Done Producing!")

    End Sub

    Public Sub Consumer(ByVal rb As RingBuffer)

        Dim deq As Integer = 0

        Using fs As New IO.FileStream("c:\test.bin", IO.FileMode.Create)
            While True
                Dim t As Tile = rb.Dequeue()
                If t Is Nothing Then Exit While                
                fs.Write(t.buffer, 0, t.buffer.Length)
                deq += 1
                Threading.Thread.Sleep(30)
            End While
        End Using

        Console.WriteLine("Total items dequeued: " & deq.ToString())
        Console.WriteLine("Done Consuming!")

    End Sub

    Sub Main()

        Dim rb As New RingBuffer(1000)

        Dim thrdProducer As New Threading.Thread(AddressOf Producer)
        thrdProducer.SetApartmentState(Threading.ApartmentState.STA)
        thrdProducer.Name = "Producer"
        thrdProducer.IsBackground = True
        thrdProducer.Start(rb)

        Dim thrdConsumer As New Threading.Thread(AddressOf Consumer)
        thrdConsumer.SetApartmentState(Threading.ApartmentState.STA)
        thrdConsumer.Name = "Consumer"
        thrdConsumer.IsBackground = True
        thrdConsumer.Start(rb)

        Console.ReadKey()

    End Sub

End Module

1 个答案:

答案 0 :(得分:0)

如果我查看消费者功能:

If rb.FullSlots = 0 And Threading.Interlocked.Read(ProducerFinished) = 0 Then
   Exit While
End If
Dim t As Tile = rb.Read()    

消费者可以找到rb.FullSlots = 0但是ProducerFinished = False并继续读取()。在Read()中,它等待writerSemaphore,但同时生产者可以完成并且永远不会释放writerSemaphore。

所以(至少)制作人应该采取措施让读者在减少ProducerFinished之后继续。

但是如果你将这个'Closing'逻辑移动到Ring缓冲区,我认为你会得到更好的设计。在那里,您可以将它与数据可用逻辑相结合。