Cross thread from a Class module - vb.net

I have a class module that uses a named pipe as a listener. It's thread is started from the UI. When it receives data I want to return the data to the UI for processing.
I've tried several examples of Invoke but none of them work. The last error message was that the UI Window's handle needed to be assigned.
The following is in the frmUDPTest:
Delegate Sub ReturnDelegate(ByVal text As String)
Public doReturn As ReturnDelegate
Public Sub DisplayReturn(ByVal ServerResponse As String)
VBL_RETURN.Text = "Server Return: " & ServerResponse
End Sub
The following is the NamedPipe receiver in a Class named ClientPipe:
Public Shared Sub Receive()
'This is on a separate thread
'This is the receiver for the Local UDPServer's return to
'the requesting client
'The pipe's name is passed to the Local UDPserver in the request
'This routine is on a seperate thread and is started prior to any
'communications to the local UDPserver.
Dim RequestBytes(64) As Byte
Dim RequestByteCount As Integer = 0
Dim Response As String
Try
frmUDPTEST.doReturn = New frmUDPTEST.ReturnDelegate(AddressOf frmUDPTEST.DisplayReturn)
ClientReceive = New NamedPipeServerStream(ClientPipeName, PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous)
While True
ClientReceive.WaitForConnection()
RequestByteCount = ClientReceive.Read(RequestBytes, 0, ByteSize)
If RequestByteCount > 0 Then
Response = Encoding.ASCII.GetString(RequestBytes) 'Convert bytes back to string
'>>>>> Problem occurs here <<<<<
frmUDPTEST.Invoke(frmUDPTEST.doReturn, New Object() {Response})
End If
ClientReceive.Disconnect()
End While
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Need the returned data from the named pipe processed by UI routines to be passed on to the client that made the request.

Related

Ping multiple device names (hostname) on the Network

A DataGridView displays hostnames at Column index 0, computer / printer names on the network.
pc1
pc2
print3
pc5
print
....
There are more than 500 such names.
I know how to ping them:
For i = 0 To DataGridView1.Rows.Count - 1
Try
If My.Computer.Network.Ping(DataGridView1.Item(0, i).Value) = True Then
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Lime
Else
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Red
End If
Catch ex As Exception
DataGridView1.Rows(i).DefaultCellStyle.BackColor = Color.Red
End Try
Next
The problem is that the Ping takes a very long time and the application freezes.
How can you speed up this procedure?
And let's say if the node is not available, then simply remove it from the list.
An example to Ping multiple addresses at the same time, using the async version of provided by the Ping class, Ping.SendPingAsync().
This version is await-able, not the same as the Ping.SendAsync() method, still asynchronous but event-driven.
Since you're using a DataGridView to both store the IpAddress/HostName and to present the PingReply results, you need to determine a way to match the Ping result to correct Cell of the DataGridView from which the Ip/Host address was taken.
Here, I'm passing to the method the Row's Index, so when the Ping result comes back, asynchronously, we can match the response to a specific Cell in the DataGridView.
To make the initialization method more generic, I'm passing also the index of the Column where the Ip/Host address is stored and the index of the Column that will show the result (you could also just pass all indexes, not a DataGridView Control reference to the method and handle the results in a different way).
A loop extracts the addresses from the the DataGridView and creates a List(Of Task), adding a PingAsync() Task for each address found.
When the collection is completed, the List(Of Task) is passed to the Task.WhenAll() method, which is then awaited.
This method starts all the Task in the list and returns when all Task have a result.
► Note that the Ping procedure sets a TimeOut, to 5000ms here, so all the Tasks will return before or within that interval, successful or not.
You can then decide if you want to reschedule the failed Pings or not.
The UI update is handled using a Progress delegate. It's just a method (Action delegate) that is called when the Ping procedure has a result to show.
It can also be used when the method that updates the UI runs in a different Thread: the Report() method will call the Progress object delegate in the Thread that created the delegate: the UI Thread, here (in the example, we're not actually ever leaving it, though).
This is how it works:
Assume you're starting the ping sequence from Button.Click event handler.
Note that the handler is declared async.
Private Async Sub btnMassPing_Click(sender As Object, e As EventArgs) Handles btnMassPing.Click
Await MassPing(DataGridView1, 1, 2)
End Sub
Initialization method and IProgress<T> report handler:
Imports System.Drawing
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Threading.Tasks
Private Async Function MassPing(dgv As DataGridView, statusColumn As Integer, addressColumn As Integer) As Task
Dim obj = New Object()
Dim tasks = New List(Of Task)()
Dim progress = New Progress(Of (sequence As Integer, reply As Object))(
Sub(report)
SyncLock obj
Dim status = IPStatus.Unknown
If TypeOf report.reply Is PingReply Then
status = DirectCast(report.reply, PingReply).Status
ElseIf TypeOf report.reply Is SocketError Then
Dim socErr = DirectCast(report.reply, SocketError)
status = If(socErr = SocketError.HostNotFound,
IPStatus.DestinationHostUnreachable,
IPStatus.Unknown)
End If
Dim color As Color = If(status = IPStatus.Success, Color.Green, Color.Red)
Dim cell = dgv(statusColumn, report.sequence)
cell.Style.BackColor = color
cell.Value = If(status = IPStatus.Success, "Online", status.ToString())
End SyncLock
End Sub)
For row As Integer = 0 To dgv.Rows.Count - 1
If row = dgv.NewRowIndex Then Continue For
Dim ipAddr = dgv(addressColumn, row).Value.ToString()
tasks.Add(PingAsync(ipAddr, 5000, row, progress))
Next
Try
Await Task.WhenAll(tasks)
Catch ex As Exception
' Log / report the exception
Console.WriteLine(ex.Message)
End Try
End Function
PingAsync worker method:
Private Async Function PingAsync(ipAddress As String, timeOut As Integer, sequence As Integer, progress As IProgress(Of (seq As Integer, reply As Object))) As Task
Dim buffer As Byte() = New Byte(32) {}
Dim ping = New Ping()
Try
Dim options = New PingOptions(64, True)
Dim reply = Await ping.SendPingAsync(ipAddress, timeOut, buffer, options)
progress.Report((sequence, reply))
Catch pex As PingException
If TypeOf pex.InnerException Is SocketException Then
Dim socEx = DirectCast(pex.InnerException, SocketException)
progress.Report((sequence, socEx.SocketErrorCode))
End If
Finally
ping.Dispose()
End Try
End Function

How can I receive many strings close to each other?

I'm making a VB.NET application that requires a very fast sending/receiving of strings ("commands") but have this little problem: when I send 2 or more consecutive string very quickly with the client, the server receives only a single string containing all the previous strings.
This is my sending Sub (Client):
Public Sub send(ByVal s As String)
Dim temp() As Byte = UTF8.GetBytes(s)
Try
stream.Write(temp, 0, temp.Length)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
This is my receiving Sub (Server, using a thread):
Public Sub listen()
Do
If client.Available > 0
Dim temp(client.Available - 1) As Byte
stream.Read(temp, 0, temp.Length)
Dim text As String = UTF8.GetString(temp)
'some checks on the string (example = if text.StartWith("something") then...)
End If
Loop
End Sub
Here's an example of what happens:
Client:
send("1"),
send("2"),
send("hello"),
send("18").
String received by Server: "12hello18".
How can I solve this?
Thanks.
You are assuming that Read reads a "message" at a time. TCP is not message-based. It offers you a boundaryless stream of bytes. Look into message framing. Often, this problem is solved by prepending the length of each message as an int before sending the message itself.

Test telnet communication on remote computer using VB.net?

I have a central server and I need to write a bit of vb.net which will see if I can telnet to a specified server on a specified port.
Is there anyway this can be done in VB.net? I thought about sending command prompts to the remote server to execute telnet, then output the logs of netsh and read those and send the information back to the central server for review.
Its a very messy way of doing it, so I was just wondering if there was an easier way
You should just create a TcpClient object with the IP address of the target server and port (typically 23 for telnet). Then call Connect!
See here for more info:
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx
Something like this (may not be exact):
Try
Dim telnetServerIp As String = "192.168.100.55"
Dim telnetPort As Integer = 23
Dim client As New TcpClient(telnetServerIp, telnetPort)
MessageBox.Show("Server is reachable")
Catch ex As Exception
MessageBox.Show("Could not reach server")
End Try
Be advised this is sample code. You'd want to clean up (close/dispose) the connection (TcpClient) object when you were done, etc. But it should get you started.
You should try something to implement this. there are lots help available for Telnet communication using .net.
Take idea from these specified links and implement in vb.net..
How can I open a telnet connection and run a few commands in C#
Telnet connection using .net
You can use a System.Net.Sockets.TcpClient object instead of a
socket object, which already has the socket parameters configured to
use ProtocolType.Tcp
1.Create a new TcpClient object, which takes a server name and a port (no IPEndPoint necessary, nice).
2.Pull a NetworkStream out of the TcpClient by calling GetStream()
3.Convert your message into bytes using Encoding.ASCII.GetBytes(string)
4.Now you can send and receive data using the stream.Write and stream.Read methods, respectively. The stream.Read method returns the number of bytes written to your receiving array, by the way.
5.Put the data back into human-readable format using Encoding.ASCII.GetString(byte array).
6.Clean up your mess before the network admins get mad by calling stream.Close() and client.Close().
Ref:C# 2.0* and Telnet - Not As Painful As It Sounds
// Create a TcpClient.
// Note, for this client to work you need to have a TcpServer
// connected to the same address as specified by the server, port
// combination.
Int32 port = 13000;
TcpClient client = new TcpClient(server, port);
// Translate the passed message into ASCII and store it as a Byte array.
Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
// Get a client stream for reading and writing.
// Stream stream = client.GetStream();
NetworkStream stream = client.GetStream();
// Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length);
Console.WriteLine("Sent: {0}", message);
I made this for a project at work.
I think it's a complete general solution for most people's needs
However, This is my first real .net project so feel free to critique it.
Modeled on http://www.codeproject.com/Articles/63201/TelnetSocket
Imports System.Threading
Imports System.IO
Imports System.IO.Pipes
Public Class TelnetClient
Private Server As String
Private NetWorkProtocolClient As System.Net.Sockets.TcpClient
Private ServerStream As System.Net.Sockets.NetworkStream
Private DoReader As Boolean
Private ReaderThread As Thread
Private OutputPipe As AnonymousPipeServerStream
Private WaitForString As String
Private WaitForStringEvent As New AutoResetEvent(False)
ReadOnly Property IsConnected() As Boolean
Get
Return (Not (IsNothing(NetWorkProtocolClient)) AndAlso (NetWorkProtocolClient.Connected))
End Get
End Property
ReadOnly Property ConnectedTo() As String
Get
If (Not (IsNothing(NetWorkProtocolClient)) AndAlso (NetWorkProtocolClient.Connected)) Then
Return NetWorkProtocolClient.Client.RemoteEndPoint.ToString()
Else
Return "Nothing"
End If
End Get
End Property
'Set the Server string to connect to.
Public Sub SetServer(ByVal new_server As String)
'double check this later
Server = new_server
End Sub
'Connects if possilbe. If already conneced to some thing it Disconnects from old Telnet and connects to new Telnet.
Public Sub Connect()
Try
If (Not (IsNothing(NetWorkProtocolClient))) AndAlso NetWorkProtocolClient.Connected Then
Disconnect()
End If
If Not IsNothing(Server) Then
NetWorkProtocolClient = New System.Net.Sockets.TcpClient(Server, 23)
If NetWorkProtocolClient.Connected Then
'clear on a new client
WaitForString = Nothing
WaitForStringEvent.Reset()
NetWorkProtocolClient.NoDelay = True
ServerStream = NetWorkProtocolClient.GetStream()
ServerStream.ReadTimeout = 1000
DoReader = True
ReaderThread = New Thread(AddressOf ReaderTask)
ReaderThread.IsBackground = True
ReaderThread.Priority = ThreadPriority.AboveNormal
ReaderThread.Start()
End If
End If
Catch ex As System.Net.Sockets.SocketException
Console.WriteLine("SocketException Connect: {0}", ex)
End Try
End Sub
'Disconnects if connected, otherwise does nothing.
Public Sub Disconnect()
Try
If ReaderThread.IsAlive Then
DoReader = False
ReaderThread.Join(1000)
End If
If (Not (IsNothing(NetWorkProtocolClient))) Then
ServerStream.Close()
NetWorkProtocolClient.Close()
End If
Catch ex As System.Net.Sockets.SocketException
Console.WriteLine("SocketException Disconnect: {0}", ex)
End Try
End Sub
'Returns true if found before timeout milliseconds. Use -1 to have infinite wait time.
'Returns false if timeout occured.
Public Function WaitFor(ByVal command As String, ByVal timeout As Integer) As Boolean
WaitForString = New String(command)
WaitForStringEvent.Reset()
Dim was_signaled As Boolean = False
'Block until a the right value from reader or user defined timeout
was_signaled = WaitForStringEvent.WaitOne(timeout)
WaitForString = Nothing
Return was_signaled
End Function
Public Sub Write(ByVal command As String)
Try
If (Not (IsNothing(NetWorkProtocolClient))) Then
If NetWorkProtocolClient.Connected Then
'Write the value to the Stream
Dim bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(command)
SyncLock ServerStream
ServerStream.Write(bytes, 0, bytes.Length)
End SyncLock
End If
End If
Catch ex As System.Net.Sockets.SocketException
Console.WriteLine("SocketException Write: {0}", ex)
End Try
End Sub
'appends CrLf for the caller
Public Sub WriteLine(ByVal command As String)
Try
If (Not (IsNothing(NetWorkProtocolClient))) Then
If NetWorkProtocolClient.Connected Then
'Write the value to the Stream
Dim bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(command & vbCrLf)
SyncLock ServerStream
ServerStream.Write(bytes, 0, bytes.Length)
End SyncLock
End If
End If
Catch ex As System.Net.Sockets.SocketException
Console.WriteLine("SocketException Write: {0}", ex)
End Try
End Sub
'Get a pipe to read output. Note: anything written by WriteLine may be echoed back if the other Telnet offers to do it.
Public Function GetPipeHandle() As String
If Not IsNothing(ReaderThread) AndAlso ReaderThread.IsAlive AndAlso Not IsNothing(OutputPipe) Then
Return OutputPipe.GetClientHandleAsString
Else
Return Nothing
End If
End Function
'Task that watches the tcp stream, passes info to the negotiation function and signals the WaitFor task.
Private Sub ReaderTask()
Try
OutputPipe = New AnonymousPipeServerStream(PipeDirection.Out)
Dim prevData As New String("")
While (DoReader)
If (Not (IsNothing(NetWorkProtocolClient))) Then
If ServerStream.DataAvailable Then
'Grab Data
Dim data As [Byte]() = New [Byte](NetWorkProtocolClient.ReceiveBufferSize) {}
Dim bytes As Integer = ServerStream.Read(data, 0, data.Length)
'Negotiate anything that came in
bytes = Negotiate(data, bytes)
If (bytes > 0) Then
'append previous to the search sting incase messages were fragmented
Dim s As New String(prevData & System.Text.ASCIIEncoding.ASCII.GetChars(data))
'If Pipe is connected send it remaining real data
If OutputPipe.IsConnected Then
OutputPipe.Write(data, 0, bytes)
End If
'Check remaining against WaitForString
If Not IsNothing(WaitForString) Then
If s.Contains(WaitForString) Then
WaitForStringEvent.Set()
'clear prevData buffer because the WaitForString was found
prevData = New String("")
Else
'Nothing found make the current string part of the next string.
prevData = New String(s)
End If
Else
prevData = New String("")
End If
End If
Else
Thread.Sleep(100)
End If
End If
End While
OutputPipe.Close()
OutputPipe.Dispose()
Catch ex As System.IO.IOException
Console.WriteLine("IO Error: {0}", ex)
Catch ex As System.Net.Sockets.SocketException
Console.WriteLine("SocketException Reader: {0}", ex)
End Try
End Sub
'Shamelessly adapted from http://www.codeproject.com/Articles/63201/TelnetSocket
'The basic algorithm used here is:
' Iterate across the incoming bytes
' Assume that an IAC (byte 255) is the first of a two- or three-byte Telnet command and handle it:
' If two IACs are together, they represent one data byte 255
' Ignore the Go-Ahead command
' Respond WONT to all DOs and DONTs
' Respond DONT to all WONTs
' Respond DO to WILL ECHO and WILL SUPPRESS GO-AHEAD
' Respond DONT to all other WILLs
' Any other bytes are data; ignore nulls, and shift the rest as necessary
' Return the number of bytes that remain after removing the Telnet command and ignoring nulls
Private Function Negotiate(ByVal data As Byte(), ByVal length As Int32) As Int32
Dim index As Int32 = 0
Dim remaining As Int32 = 0
While (index < length)
If (data(index) = TelnetBytes.IAC) Then
Try
Select Case data(index + 1)
Case TelnetBytes.IAC
data(remaining) = data(index)
remaining += 1
index += 2
Case TelnetBytes.GA
index += 2
Case TelnetBytes.WDO
data(index + 1) = TelnetBytes.WONT
SyncLock ServerStream
ServerStream.Write(data, index, 3)
End SyncLock
index += 3
Case TelnetBytes.DONT
data(index + 1) = TelnetBytes.WONT
SyncLock ServerStream
ServerStream.Write(data, index, 3)
End SyncLock
index += 3
Case TelnetBytes.WONT
data(index + 1) = TelnetBytes.DONT
SyncLock ServerStream
ServerStream.Write(data, index, 3)
End SyncLock
index += 3
Case TelnetBytes.WILL
Dim action As Byte = TelnetBytes.DONT
Select Case data(index + 2)
Case TelnetBytes.ECHO
action = TelnetBytes.WDO
Case TelnetBytes.SUPP
action = TelnetBytes.WDO
End Select
data(index + 1) = action
SyncLock ServerStream
ServerStream.Write(data, index, 3)
End SyncLock
index += 3
End Select
Catch ex As System.IndexOutOfRangeException
index = length
End Try
Else
If (data(index) <> 0) Then
data(remaining) = data(index)
remaining += 1
End If
index += 1
End If
End While
Return remaining
End Function
Private Structure TelnetBytes
'Commands
Public Const GA As Byte = 249
Public Const WILL As Byte = 251
Public Const WONT As Byte = 252
Public Const WDO As Byte = 253 'Actually just DO but is protected word in vb.net
Public Const DONT As Byte = 254
Public Const IAC As Byte = 255
'Options
Public Const ECHO As Byte = 1
Public Const SUPP As Byte = 3
End Structure
End Class

Check proxy's from listbox

So i have a listbox that i have there for a list of proxies. I have 4 buttons pertaining to it. they are find, load, save and check
I have the first 3 finished and working but i haven't found anything useful pertaining to checking the proxies, the only one that i found took like 6 seconds per proxy so it took a lot of time for a decent sized list.
So how could i make it that on the press of that button, it checks all of the proxies in the listbox and it deletes the slow ones and the ones that flat out do not work. and does this at a decent pace(so it would probably be multi threaded)
and since i can not figure this out i have no code pertaining to this except for the sub for the button click i do not feel there is a need to post code
My suggestion for you is :
1)use a timer control and set it's Tick property to an appropriate value such 500;
2) create an array of BackGroudWorkers for example BackGroudWorker[20];
3)when your app start run all BackGroudWorkers in array and in tick event of Timer check if any of this BackGroudWorker completed or not.If completed and you have other item in list then run it with new Item.Do this until all list Items checked
Public Shared Function CheckProxy(ByVal Proxy As String) As Boolean
Dim prx As Uri = Nothing
If Uri.TryCreate(Proxy, UriKind.Absolute, prx) Then
Return CheckProxy(prx)
ElseIf Uri.TryCreate("http://" & Proxy, UriKind.Absolute, prx) Then
Return CheckProxy(prx)
Else
Return False
End If
End Function
Public Shared Function CheckProxy(ByVal Proxy As Uri) As Boolean
Dim iProxy As Socket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
iProxy.ReceiveTimeout = 500 : iProxy.SendTimeout = 500
Try
'' Connect using a timeout (1/2 second)
Dim result As IAsyncResult = iProxy.BeginConnect(Proxy.Host, Proxy.Port, Nothing, Nothing)
Dim success As Boolean = result.AsyncWaitHandle.WaitOne(500, True)
If (Not success) Then
iProxy.Close() : Return False
End If
Catch ex As Exception
Return False
End Try
Dim bytData() As Byte, strData As String
Dim iDataLen As Integer = 1024
strData = String.Format("CONNECT {0}:{1} HTTP/1.0{2}{2}", "www.google.com", 80, vbNewLine)
bytData = System.Text.ASCIIEncoding.ASCII.GetBytes(strData)
If iProxy.Connected Then
iProxy.Send(bytData, bytData.Length, SocketFlags.None)
ReDim bytData(1024)
Do
Try
iDataLen = iProxy.Receive(bytData, bytData.Length, SocketFlags.None)
Catch ex As Exception
iProxy.Close() : Return False
End Try
If iDataLen > 0 Then
strData = System.Text.ASCIIEncoding.ASCII.GetString(bytData)
Exit Do
End If
Loop
Else
Return False
End If
iProxy.Close()
Dim strAttribs() As String
strAttribs = strData.Split(" "c)
If strAttribs(1).Equals("200") Then
Return True
Else
Return False
End If
End Function
You should manage your code for threads etc, as suggested by #Nima for your proxy checking problem I have 2 methods here One asks proxy string and tries to connect it.
e.g.
ProxyStatus = CheckProxy("http://192.168.1.1:8080/")
ProxyStatus is True/False depending on if proxy works or Not

.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.