The sample rate of my sound card changes slightly. Counteract? - vb.net

I read this thread because I also wanted to find out if the sample rate of my sound card is always the same or if the value fluctuates. I downloaded the Soundcheck programme recommended in the thread. According to the Windows settings, my sound card is set as follows:
Sample rate: 48000 Hz
Channels: 2
Resolution: 24 bits.
A long time ago I wrote an application that does a Fourier transform and displays the magnitudes of the individual frequencies as a bar (chart).
I have recorded a video for you because it is difficult for me to explain the problem. As you can see, according to Soundcheck, the bytes per second are changing all the time. I have also seen larger value deviations (up to ±1 %). I claim that this problem is the reason why the bars in the Fourier transformation programme jump up and down a bit, especially those that should not be visible at all.
Please note that due to the video editing programme it seems as if the played 2000 Hz test tone trills, but it does not. It is always the same volume.
Is there anything that can be done to get the true sample rate of the sound card, or is the code for getting the audio bytes not good enough?
I will delete the video in a few days.
This is the VB.NET source code to get the audio data (minimal example). You must download NAudio from Visual Studio's own NuGet package manager.
Imports Microsoft.VisualBasic.ControlChars
Public NotInheritable Class FormMain
Private continue_ As Boolean = True
''' <summary>
''' sample rate of the soundcard in Hertz
''' </summary>
Private Samplerate As Integer = 48000
''' <summary>
''' 1024
''' </summary>
Private ReadOnly BufferSize As Integer = CInt(Math.Pow(2, 10)) ' 1024
Private Shared wi As NAudio.Wave.WaveIn = Nothing
Private bwp As NAudio.Wave.BufferedWaveProvider = Nothing
''' <summary>
''' 21.333 ms
''' </summary>
Private usedMilliseconds As Double
Private cnt As Integer = 0
''' <summary>
''' to hold the latest soundcard data that we will graph. <para></para>
''' Size of this array becomes BufferSize ÷ 2, i.e., 512.
''' </summary>
Private DataArray As Double()
Private Sub AudioDataAvailable(ByVal sender As Object, e As NAudio.Wave.WaveInEventArgs)
bwp.AddSamples(e.Buffer, 0, e.BytesRecorded)
End Sub
Private Sub Start_Listening()
wi = New NAudio.Wave.WaveIn()
wi.DeviceNumber = 1
wi.WaveFormat = New NAudio.Wave.WaveFormat(Samplerate, 16, 1)
wi.BufferMilliseconds = CInt(CDbl(BufferSize) / CDbl(Samplerate) * 1000.0)
usedMilliseconds = CDbl(BufferSize) / CDbl(Samplerate) * 1000.0
AddHandler wi.DataAvailable, AddressOf AudioDataAvailable
bwp = New NAudio.Wave.BufferedWaveProvider(wi.WaveFormat)
bwp.BufferLength = BufferSize * 2
bwp.DiscardOnBufferOverflow = True
Try
wi.StartRecording()
Catch ex As NAudio.MmException
MessageBox.Show($"Could not record from audio device!{NewLine}{NewLine}{ex.Message}",
"",
MessageBoxButtons.OK,
MessageBoxIcon.Error)
End Try
End Sub
Public Sub GetLatestData()
Dim audioBytes As Byte() = New Byte(BufferSize - 1) {}
bwp.Read(audioBytes, 0, BufferSize)
If audioBytes.Length = 0 Then Return
'If audioBytes(BufferSize - 2) = 0 Then Return
Dim BYTES_PER_POINT As Integer = 2
Dim PointCount As Integer = audioBytes.Length \ BYTES_PER_POINT
DataArray = (New Double(PointCount - 1) {})
For i As Integer = 0 To PointCount - 1 Step 2
DataArray(i) = CDbl(BitConverter.ToInt16(audioBytes, i * BYTES_PER_POINT))
Next
End Sub
Private Async Sub ButtonStart_Click(sender As Object, e As EventArgs) Handles ButtonStart.Click
Start_Listening()
continue_ = True
While continue_
Await Task.Run(Sub() FT())
Await Task.Run(Sub() Plot())
End While
End Sub
Private Sub ButtonStop_Click(sender As Object, e As EventArgs) Handles ButtonStop.Click
If wi IsNot Nothing Then
wi.StopRecording()
RemoveHandler wi.DataAvailable, AddressOf AudioDataAvailable
wi.Dispose()
End If
continue_ = False
PictureBox1.Image = Nothing
GC.Collect()
End Sub
Private Sub Plot()
' plot the bars
End Sub
Private Sub FT()
GetLatestData()
If DataArray Is Nothing Then
Return
End If
' do a fourier transform here
End Sub
Private Sub NumericUpDown_Samplerate_ValueChanged(sender As Object, e As EventArgs) Handles NumericUpDown_Samplerate.ValueChanged
Me.Samplerate = CInt(NumericUpDown_Samplerate.Value)
End Sub
End Class
Addition for future readers
May 2, 2022, 11:19 a.m.,
Thank you, Hans, for this interesting article. No sarcasm. The article describes exactly the problems I am experiencing and states that this is normal and because the measurement data is finite instead of infinite.
My problems are in fact:
1.) The rectangles in the chart of wrong, neighbouring frequencies also rise.
2.) There is a wave effect, i.e., the individual bars rise and fall from right to left like a wave. “…an FFT of these data produces spectral values “riding” on a curve, not a single point at a single frequency as you might expect.”
Figure 3b best illustrates the situation: a pure 17.5 Hz sine wave shows up in the graph making the neighbouring points at 16 Hz and 18 Hz rise (instead of being peaked at 17.5 Hz).
So I assume (as of now) that the approach to get the sound card data is sufficient and there are simply some errors which are documented in scientific papers.

Related

memory mapped files disappear randomly

I have a camera management system with the following components in vb.net
This has worked well for several years, but is now randomly failing in different programs with a 'file not found' error when accessing the memory file or simply freezing. This occurred after I added a new memory file (GPS2Memory) The design of the system is to have several data readers assembling data from a their own data streams and overwriting the prior values , while 3 control applications read these records and control servos. These actions are asynchronous.
I have tried deleting the MMF.Dispose but the apps continue to fail.
CreateMemoryFiles - Creates 5 memory files (Switch_Memory, INS_Memory, GPS_Memory, AHRS_Memory, GPS2Memory) each is 400 bytes except Switch - which is 20.
ReadAHRS reads a data stream on a com port, and places a formatted csv string in AHRSMemory about every 50 ms.
ReadSecondGPS reads a data stream on a com port and places a formatted csv string in GPS2Memory about every 30 ms.
ReadIMUData reads a data stream on a com port and places a formatted csv string in IMU_Memory about every 30 ms.
ReadSwitch reads a switch status on a USB port and places a formatted csv string in SwitchMemory about every 50 ms.
BankCompensator reads IMU_Memory (for roll data), and SwitchMemory (On or Off) to start servo control.
CameraControl reads GPS2Memory (for WAAS data) IMUMemory (for pitch, roll and heading) and SwitchMemory for starting an stopping servos and shutter events.
I have run all programs in Debug mode :
ReadSecondGPS failed after 16843 cycles - Failed on 'Openexisting" statement with 'Unable to find specified file.
ReadAHRS failed after 12652 cycles, with same error and message
ReadIMUData failed after 45331 cycles with same message
CreateMemoryFiles was still running and when I set a breakpoint, it was at 3679 cycles (Incrementing every 1000 ms) and continued.
I have tested the string length written to the files, and the longest is 109 characters - I set the length longer as the strings get more data as new data becomes available.
Why would Windows lose track of 3 memory files when the initiator was still active?
Here is the create code - this is an endless loop so the memory files should not disappear.
Option Explicit On
Option Strict On
Imports System
Imports System.Net
Imports System.Text
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Timers
Imports System.Threading
Public Class Form1
Public WithEvents MEMSTimer As New System.Windows.Forms.Timer()
Dim LoopForever As Boolean = True
ReadOnly GPSINS_Memory_File_Name As String = "GPSINSMemoryData"
ReadOnly GPS2_Memory_File_Name As String = "GPRMCmemoryData"
ReadOnly AHRS_Memory_File_Name As String = "AHRSMemoryData"
ReadOnly Switch_Memory_File_Name As String = "SwitchMemoryData"
Dim CycleCount As Integer = 1
Public Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim GPS2 = MemoryMappedFile.CreateNew(GPS2_Memory_File_Name, 20, MemoryMappedFileAccess.ReadWrite)
Dim MMS = MemoryMappedFile.CreateNew(Switch_Memory_File_Name, 400, MemoryMappedFileAccess.ReadWrite)
Dim GPS = MemoryMappedFile.CreateNew(GPSINS_Memory_File_Name, 400, MemoryMappedFileAccess.ReadWrite)
Dim AHRS = MemoryMappedFile.CreateNew(AHRS_Memory_File_Name, 400, MemoryMappedFileAccess.ReadWrite)
CycleCountOut.Text = CType(CycleCount, String)
Do Until LoopForever = False
CycleCount += 1
CycleCountOut.Clear()
CycleCountOut.Text = CType(CycleCount, String)
Thread.Sleep(1000)
Loop
Application.Exit()
End Sub
Public Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
LoopForever = False
Application.Exit()
End Sub
Private Sub CycleCountOut_TextChanged(sender As Object, e As EventArgs) Handles CycleCountOut.TextChanged
End Sub
End Class
Here is a typical Write code
Sub WriteAHRS_To_Memory()
Dim MMF = MemoryMappedFile.OpenExisting(AHRS_Memory_File_Name)
Dim Bytes As Byte()
Bytes = StrToByteArray(AHRS_Data_Out)
Try
Using writer = MMF.CreateViewAccessor(0, Bytes.Length)
writer.WriteArray(Of Byte)(0, Bytes, 0, Bytes.Length)
' writer.Dispose()
End Using
Catch ex As Exception
MsgBox("mem write error = " & ex.ToString)
End Try
stop_time = Now
AHRS_elapsed_time = stop_time.Subtract(start_time)
AHRS_Update_Time.Clear()
AHRS_Update_Time.AppendText(AHRS_elapsed_time.TotalSeconds.ToString("0.000000"))
start_time = Now
MMF.Dispose()
End Sub
And typical Read code
Sub GetIMUData()
Dim MMS = MemoryMappedFile.OpenExisting(Switch_Memory_File_Name)
Using Switchreader = MMS.CreateViewAccessor(0, 20, MemoryMappedFileAccess.Read)
Dim SwitchByteString = New Byte(20) {}
Switchreader.ReadArray(Of Byte)(0, SwitchByteString, 0, SwitchByteString.Length)
outMessage = Convert.ToString(SwitchByteString)
teststring = ""
teststring = BitConverter.ToString(SwitchByteString)
For i As Integer = 0 To SwitchByteString.Length - 1
SwitchdataIn = System.Text.Encoding.ASCII.GetString(SwitchByteString)
Next
End Using
Dim SwitchString() As String
SwitchString = Split(SwitchdataIn, ",")
SwitchdataIn = SwitchString(0)
SwitchPositionDisplay.Clear()
SwitchPositionDisplay.Text = SwitchdataIn
MMS.Dispose()

Code error for contour anlaysis in VB.net

I'm trying to use emgu.cv lib for contour function in vb.net. The problem is my var is not defined. This should come under lib emgu.cv which I have already imported.
Dim borderPen As New Pen(Color.FromArgb(150, 0, 255, 0))
Dim processor As ImageProcessor
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs)
Dim borderPen As New Pen(Color.FromArgb(150, 0, 255, 0))
If RadioButton1.Checked = True Then
For Each contour As var In processor.contours
If contour.Total > 1 Then
e.Graphics.DrawLines(Pens.Red, contour.ToArray())
End If
Next
End If
SyncLock processor.foundTemplates
For Each found As FoundTemplateDesc In processor.foundTemplates
If found.template.name.EndsWith(".png") OrElse found.template.name.EndsWith(".jpg") Then
DrawAugmentedReality(found, e.Graphics)
Continue For
End If
Next
End SyncLock
End Sub
Private Sub DrawAugmentedReality(found As FoundTemplateDesc, gr As Graphics)
Dim fileName As String = "C:\Users\pnasguna\Desktop\A56.jpg"
Dim AugmentedRealityImages As New Dictionary(Of String, Image)()
Dim img As Image = AugmentedRealityImages(fileName)
Dim p As Point = found.sample.contour.SourceBoundingRect.Center()
Dim state = gr.Save()
gr.TranslateTransform(p.X, p.Y)
gr.RotateTransform(CSng(180.0F * found.angle / Math.PI))
gr.ScaleTransform(CSng(found.scale), CSng(found.scale))
gr.DrawImage(img, New Point(-img.Width / 2, -img.Height / 2))
gr.Restore(state)
End Sub
I could not compile as var is not defined. How to fix this problem?
You get the Type <typename> is not defined error because the type var is not defined. You fix this by doing one of the following steps:
Remove As var.
For Each contour In processor.contours
Replace var with the correct data type.
For Each contour As <THE_CORRECT_TYPE> In processor.contours
Emgu
Looking at the source code for emgu (written in C#), the ImageProcessor.cs file will reveal the data type of contours:
public List<Contour<Point>> contours;
Translated into vb.net:
Public contours As List(Of Contour(Of Point))
Solution
So with this information it's pretty easy to pick the correct data type.
For Each contour As Contour(Of Point) In Me.processor.contours
Note: You should always have Option Strict set to On.

Need help rewriting a foreach and do until loop

So i am trying to recode this because its not working correctly :
Private Sub StartTrafficExchange()
Dim downloadstring As New StreamReader(Response2.GetResponseStream())
Dim filterstring As String() = downloadstring.ReadToEnd.Split("|")
For Each stirngman As String In filterstring
linklist.Items.Add(stirngman)
Next
Do Until linklist.Items.Count = 0
Dim rand As New Random
linklist.SelectedIndex = rand.Next(0, linklist.Items.Count - 1)
Dim strimgna As String = linklist.SelectedItem
Dim newlinkstring As String() = strimgna.Split("``")
For Each stringma As String In newlinkstring
If stringma.Length < 8 Then
GoTo a
Else
LabelX1.Text = "Navigating To " + stringma
stringma = stringma.Replace("[TIER4]", "")
Debug.WriteLine(stringma)
WebKitBrowser1.Navigate(strimgna)
Thread.Sleep(15000)
End If
LabelX1.Text = "Navigating To " + stringma
ProgressBarX1.Value += 1 a:
Next
linklist.Items.Remove(linklist.SelectedItem)
Loop
ProgressBarX1.Maximum = linklist.Items.Count
ProgressBarX1.Value = 0
StartTrafficExchange()
End Sub
So what does the code do ? Well it fetches a webrequest to a link where the source will be like
http://www.asdfd.com``[TIER4]|http://aesde.com``[TIER4]|http://www.excaedf.com``[TIER4]|
As you can see the above code first splits "|" and adds them each to a list.
Then we split other things to make it as a perfect url and then navigate to it by webkitbrowser... and waits 15 sec by using thread.sleep (Idk if it works.) then removes it from linklist in a foreach and do until loop. So whats the problem ? Well it doesn't work correctly the webkitbrowser is just hanging .
Note : The sub StartTrafficExchange() is called when the form loads.
Can someone tell me whats wrong with the above code and is there anyother way to make this work ?
-Thanks-
Assuming that linklist is not an actual LinkedList but is really a List(Of T) or the list of a List control, you can replace this chunk of code:
Dim filterstring As String() = downloadstring.ReadToEnd.Split("|")
For Each stirngman As String In filterstring
linklist.Items.Add(stirngman)
Next
with this:
Dim filter As String() = downloadstring.ReadToEnd.Split("|")
linklist.AddRange(filter)
which does the same thing, only faster, and with less memory being consumed.
Next, move this way up (like immediately after the AddRange):
ProgressBarX1.Maximum = linklist.Items.Count
I was going to type out why to make the various changes, but here is the whole routine as I would refactor it:
Private Sub StartTrafficExchange()
' Performing one split here that removes everything that needs
' to go will be faster, take less memory, won't have to be
' touched as many times
Dim filter As String() = downloadstring.ReadToEnd.Split("``[TIER4]|")
linklist.AddRange(filter)
' Set your maximum value so it knows when it is full
ProgressBarX1.Maximum = linklist.Items.Count
' Create a counter to let us know how many items
' have been processed
Dim counter As Int = 0
Dim currentURL As String = String.Empty
Do While linklist.Items.Count > 0
' Get the current URL from our list
currentURL = linklist.Items(counter)
LabelX1.Text = String.Format("Navigating To [TIER4] {0}", currentURL)
LabelX1.Refresh() ' Allow the label to update
' This is only good if you have a debugger turned on
' or are running from the IDE
Debug.WriteLine(currentURL)
WebkitBrowser1.Navigate(currentURL)
' Not a good idea as it will block the UI
' from responding. If you need a delay that
' doesn't appear to lock up the UI, implement
' a timer in a loop
Thread.Sleep(15000)
' Increment the counter, then update the progressbar
counter += 1
ProgressBarX1.Value = counter
ProgressBarX1.Refresh() ' Allow the progressbar to update
Loop
' When you are through getting all of the URLs,
' clear the list just one time
linklist.Items.Clear()
' Make recursive call to this sub
' You should limit the number of recursions
' somehow so you don't run out of stack space
StartTrafficExchange
End Sub
If you want to pause the process with blocking the UI, use this:
' Create a new timer object that will run for 1/10 of second
Dim timr As New Timer(100)
' Run this for 150 times at .1 seconds will
' give you a 15 second pause and still leave
' the UI responsive
For iLoop As Integer = 0 To 150
' Start the timer for .1 seconds
timr.Start()
' This tells everything on the form to process updates
DoEvents()
Next
First of all, this is not very important but the code is inconsistent, you are using C# practices but you are in VB.NET, then remove all those + operator on strings appends and replace them with an &.
Second, if your purpose is to wait for the webpage to be fully loaded then using Sleep method is not the way, you need to suscribe to like an WebBrowserDocumentCompleted event of the browser control (I don't know the exact event name for a WebKitBrowser)
You can simplify things by writting a method called NavigateAndWait and use it instead of using WebKitBrowser.Navigate method.
I give you an example for a default WebBrowser control:
Private WebPageLoaded As Boolean = False
''' <summary>
''' Navigates to an url and waits the page to be loaded.
''' </summary>
''' <param name="url">Indicates the url to navigate.</param>
''' <param name="newWindow">Indicates whether the url should open into a new browser window.</param>
Private Sub NavigateAndWait(ByVal Browser As WebBrowser,
ByVal url As String,
Optional newWindow As Boolean = False)
Me.WebPageLoaded = False
AddHandler Browser.DocumentCompleted, AddressOf WebBrowserDocumentCompleted
Browser.Navigate(url, newWindow)
Do Until Me.WebPageLoaded
Application.DoEvents()
Loop
RemoveHandler Browser.DocumentCompleted, AddressOf WebBrowserDocumentCompleted
End Sub
' WebBrowser [DocumentCompleted]
Private Sub WebBrowserDocumentCompleted(ByVal sender As Object, e As WebBrowserDocumentCompletedEventArgs)
Me.WebPageLoaded = True
End Sub
And other for a GeckoFX webbrowser:
Private WebPageLoaded As Boolean = False
''' <summary>
''' Navigates to an url and waits the page to be loaded.
''' </summary>
''' <param name="url">Indicates the url to navigate.</param>
Private Sub NavigateAndWait(Byval Browser as Gecko.GeckoWebBrowser,
Byval url As String,
Optional loadFlags As Gecko.GeckoLoadFlags = Gecko.GeckoLoadFlags.None,
Optional referrer As String = Nothing,
Optional postData As Gecko.GeckoMIMEInputStream = Nothing)
Me.WebPageLoaded = False
AddHandler Browser.DocumentCompleted, AddressOf GeckoWebBrowserDocumentCompleted
Browser.Navigate(url, loadFlags, referrer, postData)
Do Until Me.WebPageLoaded
Application.DoEvents()
Loop
RemoveHandler Browser.DocumentCompleted, AddressOf GeckoWebBrowserDocumentCompleted
End Sub
' GeckoWebBrowser [DocumentCompleted]
Private Sub GeckoWebBrowserDocumentCompleted(ByVal sender As Object, e As EventArgs)
Me.WebPageLoaded = True
End Sub

Strange debugging behaviour using class library

I have a VB.NET class library, ConsoleControls, which has a few class objects designed to imitate Forms objects in Console mode. For example, there is a ProgressBar class which draws an ASCII progress bar set at a certain value. I've been using this library in my main project, TheGame.
The code is functional, but when I use the class objects and methods in my main project, I've been getting strange behaviour when debugging. For example, when I call the ProgressBar class's Draw method, the yellow arrow showing which line is about to be executed appears at the End Sub for that method. Then of all things it starts running through a completely different class, TextBox, which has nothing to do with ProgressBar. It repeats certain lines multiple times. It's as if the debugger is running a completely different portion of code to what it shows me it's running.
Here's the ProgressBar class:
Public Class ProgressBar
Public InactiveColour As ConsoleColor
Public ActiveColour As ConsoleColor
Public Position As Point
Public Length As Integer
Public Value As Integer
Public MaxValue As Integer
Public Sub New(_Position As Point, _Length As Integer, _InactiveColour As ConsoleColor, _ActiveColour As ConsoleColor)
Position = _Position
Length = _Length
InactiveColour = _InactiveColour
ActiveColour = _ActiveColour
End Sub
Public Sub SetValue(_Value As Integer, _MaxValue As Integer)
Value = _Value
MaxValue = _MaxValue
End Sub
Public Sub IncrementValue()
If Value + 1 <= MaxValue Then Value += 1
End Sub
Public Sub DecrementValue()
If Value - 1 >= 0 Then Value -= 1
End Sub
Public Sub Draw()
Dim ActiveBars, InactiveBars As Integer
Dim Divisor As Double
Divisor = MaxValue / Length
ActiveBars = CInt(Value / Divisor)
InactiveBars = Length - ActiveBars
Console.SetCursorPosition(Position.X, Position.Y)
Console.ForegroundColor = ActiveColour
For i = 1 To ActiveBars
Console.Write(Block)
Next
Console.ForegroundColor = InactiveColour
For i = 1 To InactiveBars
Console.Write(Block)
Next
Console.ResetColor()
End Sub
End Class
And here's the code that does weird things in my main project:
Dim PlayerHealthBar As ProgressBar
PlayerHealthBar = New ProgressBar(New Point(30, 4), 32, ConsoleColor.DarkGray, ConsoleColor.White)
PlayerHealthBar.SetValue(1, 10)
PlayerHealthBar.Draw()
Any suggestions as to what may be going on?

.NET Terminating Threads in an orderly fashion

Currently, I have a RingBuffer which is run by a producer and a consumer thread.
In looking for a method of terminating them orderly, I thought I'd use a flag to indicate when the producer had finished and then check that flag in my consumer along with the number of ring buffer slots that need to be written. If the producer has finished and the ring buffer has no slots that need to be written the consumer can terminate.
That works well.
However, if I artificially lengthen the time the producer takes by inserting a sleep, the consumer does not terminate. I believe this is a consequence of the semaphores being used.
Here is the code I'm working with. Notice that the program will "hang" after all slots have been written. The producer terminates, but the consumer "hangs".
Any advice on terminating both in an orderly fashion would be greatly appreciated.
Edit - Updated code with Henk's suggestion of using a Queue. +1000 points to the first person to suggest a better method of terminating the consumer/producer threads than either knowing the exact amount of items being worked with or returning a value such as null/nothing indicating that no more items exist in the queue (though this doesn't mean they aren't still being produced.)
Edit - I believe I've figured it out. Simply pass null or nothing to RingBuffer.Enqueue for each consumer and catch the null or nothing object in the consumer to terminate it. Hopefully someone finds this useful.
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
If I look at the Consumer function:
If rb.FullSlots = 0 And Threading.Interlocked.Read(ProducerFinished) = 0 Then
Exit While
End If
Dim t As Tile = rb.Read()
The consumer could find rb.FullSlots = 0 but ProducerFinished = False and continue to Read(). Inside Read() it waits for the writerSemaphore but in the mean time the Producer could finish and never release the writerSemaphore.
So (at least) the producer should take steps to let the readers continue after it decreases the ProducerFinished.
But I think you get a better design if you move this 'Closing' logic to the Ring buffer. There you can combine it with the Data-available logic.