Calling a Sub that Connects to DB from Thread - vb.net

I'm writing a VB.net 2017 Windows Service that looks at SQL and depending on the number of rows, it will create multiple threads. These threads monitor folders and report back to a different table and log the data accordingly. This piece of code has been running for a few years and has been working very well but in the last few days, I've decided to switch it from a console application that runs on start up to a window service and this is my first time writing a windows service.
I've went through and got the code to work but testing was a major pain because I couldn't walk through the code. I made some changes and consolidated some of the duplicate sections. For example, I was writing 4 or 5 different sections of code to either write data to SQL or pull data from SQL. I consolidated these down to only 2 sub routines and the threads continuously use them. Depending on the situation, the program can have between 1-15 threads and when I start activating more threads, I started to run into an issue. I already had try statements in my code from the console app before I ported it and I just put these into a log table when creating the new program and it was complaining that I was trying to Open an "Open connection". Here is an example of one the routines that pulls data from SQL:
Public con As New SqlClient.SqlConnection
Public dsGeneral As New DataSet
Public dc1 As SqlClient.SqlCommand
Public pullGeneral As SqlClient.SqlDataAdapter
Public maxRowsGeneral As Integer
Public Sub PullGeneralSQL(ByVal SQL As String)
Try
If (con.State = ConnectionState.Closed) Then
con.Open()
End If
dsGeneral.Tables.Clear()
pullGeneral = New SqlClient.SqlDataAdapter(SQL, con)
pullGeneral.Fill(dsGeneral, "General")
maxRowsGeneral = dsGeneral.Tables("General").Rows.Count
Catch ex As Exception
Msg(ex.Message, "Error")
maxRowsGeneral = 0
End Try
con.Close()
End Sub
I'm also getting errors saying that the connection is already closed as well. I'm assuming that another thread has finished connecting and closed the connection while a thread was in the middle of a task.
My question is, what is the best way to handle this?

I have run into this issue before even though there is connection pooling eventually as you open and close and open and close connections the database ends up throwing errors like you are getting. In my case it was 600 threads over and over. Worked for a little while then threw the same types of errors.
The solution for me was to create my own connection pool.
Public Class Form1
Public dsGeneral As New DataSet
Public dc1 As SqlClient.SqlCommand
Public pullGeneral As SqlClient.SqlDataAdapter
Public maxRowsGeneral As Integer
Public Sub PullGeneralSQL(ByVal SQL As String)
'Get a connection from the list..Thread safe
'Your thread will wait here until there is a connection to grab.
'Since your max is 15 the 16++ threads will all wait their turn
Dim con As SqlClient.SqlConnection = ConnectionManager.WaitForConnection()
Try
dsGeneral.Tables.Clear()
pullGeneral = New SqlClient.SqlDataAdapter(SQL, con)
pullGeneral.Fill(dsGeneral, "General")
maxRowsGeneral = dsGeneral.Tables("General").Rows.Count
Catch ex As Exception
Msg(ex.Message, "Error")
maxRowsGeneral = 0
End Try
'Put the connection back into the list
'Allows another thread to start
ConnectionManager.ReleaseConnection(con)
End Sub
End Class
Public Class ConnectionManager
Public Shared poolLimit As Integer = 15 'Maximum number of connections to open.
Public Shared connectionString As String = "PUT YOUR CONNECTION STRING HERE"
'Since this is static it will create 15 connections and add them to the list when the service starts
Private Shared ConnectionList As New IThreadPool(Of SqlClient.SqlConnection)(Function()
Dim connection As New SqlClient.SqlConnection(connectionString)
connection.Open()
Return connection
End Function, poolLimit)
''' <summary>
''' Gets the pool count.
''' Purely for information to get the number of connections left in the list
''' </summary>
Public ReadOnly Property PoolCount() As Integer
Get
Return ConnectionList.PoolCount
End Get
End Property
''' <summary>
''' Waits until there is a free connection in the list
''' When there is a free connection grab it and hold it
''' </summary>
Public Shared Function WaitForConnection() As SqlClient.SqlConnection
Try
Return ConnectionList.GetObject()
Catch ex As Exception
'only during close
Throw ex
End Try
Return Nothing
End Function
''' <summary>
''' Releases the connection.
''' Put the connection back into the list.
''' </summary>
Public Shared Sub ReleaseConnection(connection As SqlClient.SqlConnection)
Try
ConnectionList.PutObject(connection)
Catch ex As Exception
'only during close
Throw ex
End Try
End Sub
''' <summary>
''' Disposes this instance.
''' Make sure to dispose when the service shuts down or your connections will stay active.
''' </summary>
Public Shared Sub Dispose()
ConnectionList.Dispose()
End Sub
End Class
Public Class IThreadPool(Of T)
Private connections As System.Collections.Concurrent.BlockingCollection(Of T)
Private objectGenerator As Func(Of T)
Public Sub New(objectGenerator As Func(Of T), boundedCapacity As Integer)
If objectGenerator Is Nothing Then
Throw New ArgumentNullException("objectGenerator")
End If
connections = New System.Collections.Concurrent.BlockingCollection(Of T)(New System.Collections.Concurrent.ConcurrentBag(Of T)(), boundedCapacity)
Me.objectGenerator = objectGenerator
Task.Factory.StartNew(Function()
If connections.Count < boundedCapacity Then
Parallel.[For](0, boundedCapacity, Function(i)
Try
If connections.Count < boundedCapacity Then
connections.Add(Me.objectGenerator())
End If
Catch ex As Exception
'only error during close
End Try
End Function)
Try
While connections.Count < boundedCapacity
connections.Add(Me.objectGenerator())
End While
Catch ex As Exception
'only error during close
End Try
End If
End Function)
End Sub
Public ReadOnly Property PoolCount() As Integer
Get
Return If(connections IsNot Nothing, connections.Count, 0)
End Get
End Property
Public Function GetObject() As T
Return connections.Take()
End Function
Public Sub PutObject(item As T)
connections.Add(item)
End Sub
Public Sub Dispose()
connections.Dispose()
End Sub
End Class

Related

Methods to use the serial port in multiple forms

I'm designing a windows userform to interact with a microcontroller through the serial port.
The GUI includes multiple userforms which will use the same serialport. I researched how to do that and I found different ideas. Some I don't know if it works the others I'm not sure how to implement it in code. Let's say I have
Form1: Start.vb
Form2: Shield1.vb
1) Can I declare the serial port in the start userform as:
Public Shared SerialPort1 As New System.IO.Ports.SerialPort
And use it in the other forms ?
2) First alternative: Use a Module to declare a new Serialport
Module Module1
Public WithEvents mySerialPort1 As New IO.Ports.SerialPort
Private Sub mySerialPort1_DataReceived(sender As Object, _
e As System.IO.Ports.SerialDataReceivedEventArgs) _
Handles mySerialPort1.DataReceived
End Sub
End Module
Is this method right? If yes How do I use it in the code of my forms ? How to include the DataReceived Event in the code of the forms ?
3) Second alternative: Constructor of the Serialport in the start form and then pass the data to the other forms as mentionned in this post: Alternate Solution 1
private void OnSetup(object sender, EventArgs e)
{
this.port = new SerialPort(...);
// TODO: initialize port
Form2 f2 = new Form2(this.port);
f2.Show();
Form3 f3 = new Form3(this.port);
f3.Show();
Form4 f4 = new Form4(this.port);
f4.Show();
}
Are then the Events also included ? How do I use them?
4) Third alternative: Use a static class like done in this solution:
Alternate Solution 2
Is the code written in C# here right ? I'm writing my programm in VB.net but I could take this as a reference.
Which is the recommended solution for a beginner ? Could you please write it in a small code if you have another suggestion or a correction ?
I apologize in advance for any ambiguity or falsly used terms.
Thanks you!
I would follow the "Singleton" design pattern, which ensures that only one instance of a class is created. Here is a well-accepted template for such a class:
Public NotInheritable Class MySerial
Private Shared ReadOnly _instance As New Lazy(Of MySerial)(Function() New
MySerial(), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication)
Private Sub New()
End Sub
Public Shared ReadOnly Property Instance() As MySerial
Get
Return _instance.Value
End Get
End Property
End Class
In the New() method you should set up your serial port as you need to. Then no matter where you need to use the port, you make your references to the Instance:
Dim singletonSerial As MySerial = MySerial.Instance
This is the canonical pattern to ensure that you have only one copy of an object without resorting to static classes. It's a design pattern that dates back more than 20 years and still works great when you need exactly one copy of an object.
I found this very helpful example of a sigleton C# class for serial port:
C# Singleton Example
As a beginner I used a Code converter to have it in VB.Net. Can you guys let me know if the resulting code is correct and I can use it ? Thanks a lot!!
Imports System
Imports System.IO
Imports System.IO.Ports
Imports System.Threading
Namespace HeiswayiNrird.Singleton
Public NotInheritable Class SerialPortManager
Private Shared lazy As Lazy(Of SerialPortManager) = New Lazy(Of SerialPortManager)(() => { }, New SerialPortManager)
Public Shared ReadOnly Property Instance As SerialPortManager
Get
Return lazy.Value
End Get
End Property
Private _serialPort As SerialPort
Private _readThread As Thread
Private _keepReading As Boolean
Private Sub New()
MyBase.New
Me._serialPort = New SerialPort
Me._readThread = Nothing
Me._keepReading = false
End Sub
''' <summary>
''' Update the serial port status to the event subscriber
''' </summary>
Public Event OnStatusChanged As EventHandler(Of String)
''' <summary>
''' Update received data from the serial port to the event subscriber
''' </summary>
Public Event OnDataReceived As EventHandler(Of String)
''' <summary>
''' Update TRUE/FALSE for the serial port connection to the event subscriber
''' </summary>
Public Event OnSerialPortOpened As EventHandler(Of Boolean)
''' <summary>
''' Return TRUE if the serial port is currently connected
''' </summary>
Public ReadOnly Property IsOpen As Boolean
Get
Return Me._serialPort.IsOpen
End Get
End Property
''' <summary>
''' Open the serial port connection using basic serial port settings
''' </summary>
''' <param name="portname">COM1 / COM3 / COM4 / etc.</param>
''' <param name="baudrate">0 / 100 / 300 / 600 / 1200 / 2400 / 4800 / 9600 / 14400 / 19200 / 38400 / 56000 / 57600 / 115200 / 128000 / 256000</param>
''' <param name="parity">None / Odd / Even / Mark / Space</param>
''' <param name="databits">5 / 6 / 7 / 8</param>
''' <param name="stopbits">None / One / Two / OnePointFive</param>
''' <param name="handshake">None / XOnXOff / RequestToSend / RequestToSendXOnXOff</param>
Public Sub Open(Optional ByVal portname As String = "COM1", Optional ByVal baudrate As Integer = 9600, Optional ByVal parity As Parity = Parity.None, Optional ByVal databits As Integer = 8, Optional ByVal stopbits As StopBits = StopBits.One, Optional ByVal handshake As Handshake = Handshake.None)
Me.Close
Try
Me._serialPort.PortName = portname
Me._serialPort.BaudRate = baudrate
Me._serialPort.Parity = parity
Me._serialPort.DataBits = databits
Me._serialPort.StopBits = stopbits
Me._serialPort.Handshake = handshake
Me._serialPort.ReadTimeout = 50
Me._serialPort.WriteTimeout = 50
Me._serialPort.Open
Me.StartReading
Catch As IOException
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, String.Format("{0} does not exist.", portname))
End If
Catch As UnauthorizedAccessException
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, String.Format("{0} already in use.", portname))
End If
Catch ex As Exception
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, ("Error: " + ex.Message))
End If
End Try
If Me._serialPort.IsOpen Then
Dim sb As String = StopBits.None.ToString.Substring(0, 1)
Select Case (Me._serialPort.StopBits)
Case StopBits.One
sb = "1"
Case StopBits.OnePointFive
sb = "1.5"
Case StopBits.Two
sb = "2"
End Select
Dim p As String = Me._serialPort.Parity.ToString.Substring(0, 1)
Dim hs As String = "No Handshake"
'TODO: Warning!!!, inline IF is not supported ?
(Me._serialPort.Handshake = Handshake.None)
Me._serialPort.Handshake.ToString
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, String.Format("Connected to {0}: {1} bps, {2}{3}{4}, {5}.", Me._serialPort.PortName, Me._serialPort.BaudRate, Me._serialPort.DataBits, p, sb, hs))
End If
If (Not (OnSerialPortOpened) Is Nothing) Then
OnSerialPortOpened(Me, true)
End If
Else
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, String.Format("{0} already in use.", portname))
End If
If (Not (OnSerialPortOpened) Is Nothing) Then
OnSerialPortOpened(Me, false)
End If
End If
End Sub
''' <summary>
''' Close the serial port connection
''' </summary>
Public Sub Close()
Me.StopReading
Me._serialPort.Close
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, "Connection closed.")
End If
If (Not (OnSerialPortOpened) Is Nothing) Then
OnSerialPortOpened(Me, false)
End If
End Sub
''' <summary>
''' Send/write string to the serial port
''' </summary>
''' <param name="message"></param>
Public Sub SendString(ByVal message As String)
If Me._serialPort.IsOpen Then
Try
Me._serialPort.Write(message)
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, String.Format("Message sent: {0}", message))
End If
Catch ex As Exception
If (Not (OnStatusChanged) Is Nothing) Then
OnStatusChanged(Me, String.Format("Failed to send string: {0}", ex.Message))
End If
End Try
End If
End Sub
Private Sub StartReading()
If Not Me._keepReading Then
Me._keepReading = true
Me._readThread = New Thread(ReadPort)
Me._readThread.Start
End If
End Sub
Private Sub StopReading()
If Me._keepReading Then
Me._keepReading = false
Me._readThread.Join
Me._readThread = Nothing
End If
End Sub
Private Sub ReadPort()
While Me._keepReading
If Me._serialPort.IsOpen Then
'byte[] readBuffer = new byte[_serialPort.ReadBufferSize + 1];
Try
'int count = _serialPort.Read(readBuffer, 0, _serialPort.ReadBufferSize);
'string data = Encoding.ASCII.GetString(readBuffer, 0, count);
Dim data As String = Me._serialPort.ReadLine
If (Not (OnDataReceived) Is Nothing) Then
OnDataReceived(Me, data)
End If
Catch As TimeoutException
End Try
Else
Dim waitTime As TimeSpan = New TimeSpan(0, 0, 0, 0, 50)
Thread.Sleep(waitTime)
End If
End While
End Sub
End Class
End Namespace

global variable defined in winforms not working as expected

I have a project in VB.Net that requires a global variable (a list of event log objects) throughout. This seemed simple enough, just define the variable and initialize in the Application's StartUp event, then access it throughout the application. Unfortunately, this only seems to work when there are no child processes (they have no access to the global variable) - so I'm way over my head as how to access the global variable from the child worker processes (if that is even possible).
The program starts several Test worker processes (that check multiple DB connections from different sources, remote web services from several sources, network checks, etc) w/ progress bars for each. If an error occurs during any of these tests, the error needs to be logged.
The problem is that, the program cannot log events to the Windows Event system because it won't be running under an administrator account (so logging there is not possible thanks to MS's decision to prevent logging under normal user accounts w/Vista,7,8,10), the program also can't log to a text file due to it being asynchronous and the file access contention problems (immediately logging an event to a text file won't work), so I wish to log any events/errors in memory (global variable), THEN dump it to a log file AFTER all child processes complete. Make any sense?
I created a class called AppEvent
Public Class AppEvent
Sub New()
EventTime_ = Date.Now
Level_ = EventLevel.Information
Description_ = String.Empty
Source_ = String.Empty
End Sub
Private EventTime_ As Date
Public Property EventTime() As Date
Get
Return EventTime_
End Get
Set(ByVal value As Date)
EventTime_ = value
End Set
End Property
Private Level_ As EventLevel
Public Property Level() As EventLevel
Get
Return Level_
End Get
Set(ByVal value As EventLevel)
Level_ = value
End Set
End Property
Private Description_ As String
Public Property Description() As String
Get
Return Description_
End Get
Set(ByVal value As String)
Description_ = value
End Set
End Property
Private Source_ As String
Public Property Source() As String
Get
Return Source_
End Get
Set(ByVal value As String)
Source_ = value
End Set
End Property
End Class
Public Enum EventLevel
[Information]
[Warning]
[Error]
[Critical]
[Fatal]
End Enum
And create a public variable just for this (and add an initial event to the AppEvents list)
Namespace My
Partial Friend Class MyApplication
'global variable here (using this for logging asynch call errors, then dumping this into a log file when all asynch calls are complete (due to file contention of log file)
Public AppEvents As New List(Of AppEvent)
Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
'create first event and add it to the global variable declared above
AppEvents.Add(New AppEvent With {.EventTime = Now, .Description = "Program Started", .Level = EventLevel.Information, .Source = "QBI"})
End Sub
End Class
End Namespace
Next, in my logging class, I have some methods for logging, flushing/writing the event(s)
Public Class AppLogging
Public Shared Sub WriteEventToAppLog(evt As AppEvent)
LogDataToFile(FormatLineItem(evt.EventTime, evt.Level, evt.Description, evt.Source))
End Sub
Public Shared Sub WriteEventsToAppLog(AppEvents As List(Of AppEvent))
Dim sbEvents As New StringBuilder
If AppEvents.Count > 0 Then
For Each evt In AppEvents
sbEvents.AppendLine(FormatLineItem(evt.EventTime, evt.Level, evt.Description, evt.Source))
Next
LogDataToFile(sbEvents.ToString.TrimEnd(Environment.NewLine))
End If
End Sub
Private Shared Function FormatLineItem(eventTime As Date, eventLevel As EventLevel, eventDescr As String, eventSource As String) As String
Return String.Format("Logged On: {0} | Level: {1} | Details: {2} | Source: {3}", eventTime, System.Enum.GetName(GetType(EventLevel), eventLevel).Replace("[", "").Replace("]", ""), eventDescr, eventSource)
End Function
Private Shared Sub LogDataToFile(eventLogText As String, Optional ByVal LogFileName As String = "Error.log", Optional ByVal HeaderLine As String = "****** Application Log ******")
'log file operations
Dim LogPath As String = System.AppDomain.CurrentDomain.BaseDirectory()
If Not LogPath.EndsWith("\") Then LogPath &= "\"
LogPath &= LogFileName
Dim fm As FileMode
Try
If System.IO.File.Exists(LogPath) Then
fm = FileMode.Append
Else
fm = FileMode.Create
eventLogText = HeaderLine & Environment.NewLine & eventLogText
End If
Using fs As New FileStream(LogPath, fm, FileAccess.Write)
Using sw As New StreamWriter(fs)
sw.WriteLine(eventLogText)
End Using
End Using
My.Application.AppEvents.Clear() 'clears the global var
Catch ex As Exception
'handle this
End Try
End Sub
Public Shared Sub WriteEventToMemory(eventLevel As EventLevel, eventDescription As String, Optional eventSource As String = "")
Dim evt As New AppEvent
evt.Description = eventDescription
evt.Level = eventLevel
evt.EventTime = Now
evt.Source = eventSource
Try
My.Application.AppEvents.Add(evt)
Catch ex As Exception
Throw
End Try
End Sub
Public Shared Sub FlushEventsToLogFile()
WriteEventsToAppLog(My.Application.AppEvents)
End Sub
End Class
There's a few methods in here, but the method called in every exception handler is WriteEventToMemory (it merely adds an AppEvent to the AppEvents list).
An example test routine/worker process (to the local database) looks like:
#Region "local db test"
Private Sub TestLocalDBWorkerProcess_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestLocalDBWorkerProcess.DoWork
Me.TestLocalDBStatusMessage = TestLocalDB()
End Sub
Private Sub TestLocalDBWorkerProcess_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles TestLocalDBWorkerProcess.RunWorkerCompleted
Me.prgLocalDatabase.Style = ProgressBarStyle.Blocks
Me.prgLocalDatabase.Value = 100
If Me.ForcedCancelTestLocalDB Then
Me.lbStatus.Items.Add("Local DB Test Cancelled.")
Else
If TestLocalDBStatusMessage.Length > 0 Then
Me.lblLocalDatabaseStatus.Text = "Fail"
Me.lbStatus.Items.Add(TestLocalDBStatusMessage)
SendMessage(Me.prgLocalDatabase.Handle, 1040, 2, 0) 'changes color to red
Else
Me.lblLocalDatabaseStatus.Text = "OK"
Me.lbStatus.Items.Add("Connection to local database is good.")
Me.prgLocalDatabase.ForeColor = Color.Green
End If
End If
Me.ForcedCancelTestLocalDB = False
Me.TestLocalDBProcessing = False
ProcessesFinished()
End Sub
Private Sub StartTestLocalDB()
Me.prgLocalDatabase.Value = 0
Me.prgLocalDatabase.ForeColor = Color.FromKnownColor(KnownColor.Highlight)
Me.prgLocalDatabase.Style = ProgressBarStyle.Marquee
Me.TestLocalDBProcessing = True
Me.TestLocalDBStatusMessage = String.Empty
Me.lblLocalDatabaseStatus.Text = String.Empty
TestLocalDBWorkerProcess = New System.ComponentModel.BackgroundWorker
With TestLocalDBWorkerProcess
.WorkerReportsProgress = True
.WorkerSupportsCancellation = True
.RunWorkerAsync()
End With
End Sub
Private Function TestLocalDB() As String
Dim Statusmessage As String = String.Empty
Try
If Me.TestLocalDBWorkerProcess.CancellationPending Then
Exit Try
End If
If Not QBData.DB.TestConnection(My.Settings.DBConnStr3) Then
Throw New Exception("Unable to connect to local database!")
End If
Catch ex As Exception
Statusmessage = ex.Message
AppLogging.WriteEventToMemory(EventLevel.Fatal, ex.Message, "TestLocalDB")
End Try
Return Statusmessage
End Function
#End Region
The try-catch block simply catches the exception and writes it to memory (I just wrapped it in the WriteEventToMemory method, but it's just adding it to the AppEvents list: My.Application.AppEvents.Add(evt)
Everything appeared to be working peachy, until I noticed that the count for AppEvents was (1) after the Startup event, then it's count was (0) from any of the child processes, finally, the count was (1) when the list was dumped to the error log file (only the first event added was there). It is clearly acting like there are multiple versions of the AppEvents variable.
****** Application Log ******
Logged On: 10/7/2016 6:01:45 PM | Level: Information | Details: Program Started | Source: QBI
Only the first event shows up, the other events not (they are added, there's no null ref exceptions or any exceptions - like phantoms). Any event added to the global variable on the MAIN thread stays (and gets logged, ultimately). So this is clearly a multithreaded issue (never tried this before in a Windows app).
Any ideas on how to remedy?
As mentioned above, I had to pass the events back to the calling workerprocess, so, in the main form I put in:
Private AppEvent_TestLocalDB As New AppEvent
In the DoWork (for each process), I changed it to:
Private Sub TestLocalDBWorkerProcess_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestLocalDBWorkerProcess.DoWork
Me.TestLocalDBStatusMessage = TestLocalDB(AppEvent_TestLocalDB)
End Sub
The TestLocalDB sub now looks like:
Private Function TestLocalDB(ByRef aEvent As AppEvent) As String
Dim Statusmessage As String = String.Empty
Try
If Me.TestLocalDBWorkerProcess.CancellationPending Then
Exit Try
End If
If Not QBData.DB.TestConnection(My.Settings.DBConnStr3) Then
Throw New Exception("Unable to connect to local database!")
End If
Catch ex As Exception
Statusmessage = ex.Message
With aEvent
.Description = ex.Message
.Level = EventLevel.Fatal
.Source = "TestLocalDB"
End With
End Try
Return Statusmessage
End Function
Note there is no error logging, just the event variable (ByRef to pass it back, the equivalent to C# out).
When the worker process completes, I add the following:
With AppEvent_TestLocalDB
AppLogging.WriteEventToMemory(.Level, .Description, .Source)
End With
(the other worker processes work the same way)
When ALL the processes are complete, then I flush it to the log file.
AppLogging.FlushEventsToLogFile()
Now the custom event/error log entries look like so (with a freshly made file):
****** Application Log ******
Logged On: 10/7/2016 10:14:36 PM | Level: Information | Details: Program Started | Source: QBI
Logged On: 10/7/2016 10:14:53 PM | Level: Fatal | Details: Unable to connect to local database! | Source: TestLocalDB
That was it - just pass the variable back to the caller

Creating an object when named pipe receives message

I have been creating a single instance application using a Mutex.
In the Sub Main code, the app checks to see if it is the first instance, if so it starts the form (called MainForm). The MainForm creates an asynchronous named pipe server to receive arguments passed from a new instance.
If the app is not the first instance, Sub Main creates a named pipe client, sends the command line arguments through to the first app, and proceeds to exit.
The application is tab-based, and each command line argument is a file path, which is used to create the tab. The argument is received (I can MsgBox() it), but when I try to pass it as an argument to the control I'm creating, nothing happen
Pipe classes:
Namespace Pipes
' Delegate for passing received message back to caller
Public Delegate Sub DelegateMessage(Reply As String)
Public Class PipeServer
Public Event PipeMessage As DelegateMessage
Private _pipeName As String
Public Sub Listen(PipeName As String)
Try
' Set to class level var so we can re-use in the async callback method
_pipeName = PipeName
' Create the new async pipe
Dim pipeServer As New NamedPipeServerStream(PipeName, PipeDirection.[In], 1, PipeTransmissionMode.[Byte], PipeOptions.Asynchronous)
' Wait for a connection
pipeServer.BeginWaitForConnection(New AsyncCallback(AddressOf WaitForConnectionCallBack), pipeServer)
Catch oEX As Exception
Debug.WriteLine(oEX.Message)
End Try
End Sub
Private Sub WaitForConnectionCallBack(iar As IAsyncResult)
Try
' Get the pipe
Dim pipeServer As NamedPipeServerStream = DirectCast(iar.AsyncState, NamedPipeServerStream)
' End waiting for the connection
pipeServer.EndWaitForConnection(iar)
Dim buffer As Byte() = New Byte(254) {}
' Read the incoming message
pipeServer.Read(buffer, 0, 255)
' Convert byte buffer to string
Dim stringData As String = Encoding.UTF8.GetString(buffer, 0, buffer.Length)
Debug.WriteLine(stringData + Environment.NewLine)
' Pass message back to calling form
RaiseEvent PipeMessage(stringData)
' Kill original sever and create new wait server
pipeServer.Close()
pipeServer = Nothing
pipeServer = New NamedPipeServerStream(_pipeName, PipeDirection.[In], 1, PipeTransmissionMode.[Byte], PipeOptions.Asynchronous)
' Recursively wait for the connection again and again....
pipeServer.BeginWaitForConnection(New AsyncCallback(AddressOf WaitForConnectionCallBack), pipeServer)
Catch
Return
End Try
End Sub
End Class
Class PipeClient
Public Sub Send(SendStr As String, PipeName As String, Optional TimeOut As Integer = 1000)
Try
Dim pipeStream As New NamedPipeClientStream(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous)
' The connect function will indefinitely wait for the pipe to become available
' If that is not acceptable specify a maximum waiting time (in ms)
pipeStream.Connect(TimeOut)
Debug.WriteLine("[Client] Pipe connection established")
Dim _buffer As Byte() = Encoding.UTF8.GetBytes(SendStr)
pipeStream.BeginWrite(_buffer, 0, _buffer.Length, AddressOf AsyncSend, pipeStream)
Catch oEX As TimeoutException
Debug.WriteLine(oEX.Message)
End Try
End Sub
Private Sub AsyncSend(iar As IAsyncResult)
Try
' Get the pipe
Dim pipeStream As NamedPipeClientStream = DirectCast(iar.AsyncState, NamedPipeClientStream)
' End the write
pipeStream.EndWrite(iar)
pipeStream.Flush()
pipeStream.Close()
pipeStream.Dispose()
Catch oEX As Exception
Debug.WriteLine(oEX.Message)
End Try
End Sub
End Class
End Namespace
MainForm logic:
#Region "Pipes"
Public ArgumentPipe As New Pipes.PipeServer
Public Sub RecievedMessage(reply As String)
GetMainformHook.Invoke(MySTDelegate, reply)
End Sub
Public Sub InitializeServer()
AddHandler ArgumentPipe.PipeMessage, AddressOf RecievedMessage
ArgumentPipe.Listen(_pipename)
End Sub
Public Delegate Sub RecievedMessageDel(txt As String)
Public MySTDelegate As RecievedMessageDel = AddressOf SetText
Public Sub SetText(txt)
MsgBox(txt)
TabStrip1.AddTab(txt.ToString) 'PROBLEM OCCURS HERE
End Sub
Public Shared Function GetMainformHook() As MainForm
Return Application.OpenForms("MainForm")
End Function
Public Shared Function GetTabControl() As TabStrip
Return CType(Application.OpenForms("MainForm"), MainForm).TabStrip1
End Function
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitializeServer()
End Sub
#End Region
In Sub Main when sending argument:
Dim _pipeClient = New Pipes.PipeClient()
If cmdArgs.Length > 0 Then
For i = 0 To cmdArgs.Length - 1
_pipeClient.Send(cmdArgs(i), _pipename, 1000)
Next
End If
_pipename is a global string like "myappv6"
Am I missing something?
I'm thinking this has something to do with cross threading, but can't pinpoint where to fix it.
Thanks

How to use Async/Await in Silverlight 5 (VB)

I'm trying to call a web service and have my code wait for that service to return a result (or timeout). My project is Silverlight 5 with Web Services using .NET 4.0 and I'm running this project under VS 2012 with the Microsoft.Bcl.Async.1.0.16\lib\sl4\Microsoft.Threading.Tasks.dll ... Task.Extensions.dll ... and Extensions.Silverlight.dll.
This is how I've been doing it and it's working, but I'm trying to figure out how to change my code so that I can use the Async/Await process. The web service reference is configured to return ObservableCollection and Generic.Dictionary with Reuse types in all referenced assemblies.
Some of my code I need to convert to Async/Await:
Private _Units As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
Public Property Units() As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
Get
Return _Units
End Get
Set(ByVal value As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
_Units = value
OnPropertyChanged(New PropertyChangedEventArgs("Units"))
End Set
End Property
Public Sub ReadUnits()
Try
' Client is required
If Not Me.Client Is Nothing Then
' User is required
If Not Me.User Is Nothing Then
' Must be a real Client
If Me.Client.ClientID > 0 Then
' My have a sites
If Not Me.Site Is Nothing Then
' Call the web service relative to where this application is running
Dim webServiceURI As New Uri("../WebServices/Unit.svc", UriKind.RelativeOrAbsolute)
Dim webServiceAddress As New EndpointAddress(webServiceURI)
' Setup web Service proxy
Dim wsUnits As New DC.SL.Services.WebServiceUnit.UnitClient
wsUnits.Endpoint.Address = webServiceAddress
' Add event handler so we can trap for web service completion
AddHandler wsUnits.LoadsCompleted, AddressOf LoadUnitsCompleted
' Call web service to get Sites the user has access to
wsUnits.LoadsAsync(Me.Client, Me.Site.SiteID, Me.Size.SizeID, Me.RentalType.RentalTypeID, Me.UnitState)
End If
End If
End If
End If
Catch ex As Exception
Dim Problem As New DC.SL.Tools.Errors(ex)
End Try
End Sub
Private Sub LoadUnitsCompleted(ByVal sender As Object, ByVal e As DC.SL.Services.WebServiceUnit.LoadsCompletedEventArgs)
Try
If Not IsNothing(e.Result) Then
Me.Units = e.Result
If Me.Units.Count > 0 Then
Me.Unit = Me.Units.Item(0)
End If
End If
Catch ex As Exception
Dim Problem As New DC.SL.Tools.Errors(ex)
End Try
End Sub
Still not getting this to work ... here is what I have now, but the problem remains ... UI thread execution continues and does NOT wait for the Web Service call to finish.
Calling code:
ReadUnitsAsync().Wait(3000)
Here is the updated code:
Public Async Function ReadUnitsAsync() As Task(Of Boolean)
Dim Results As Object = Await LoadReadUnitsAsync()
Return True
End Function
Public Function LoadReadUnitsAsync() As Task(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
LoadReadUnitsAsync = Nothing
Dim tcs = New TaskCompletionSource(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
' Client is required
If Not Me.Client Is Nothing Then
' User is required
If Not Me.User Is Nothing Then
' Must be a real Client associated
If Me.Client.ClientID > 0 Then
' Only get associated sites IF we don't have any defined
If Not Me.Site Is Nothing Then
' Call the web service relative to where this application is running
Dim webServiceURI As New Uri("../WebServices/Unit.svc", UriKind.RelativeOrAbsolute)
Dim webServiceAddress As New EndpointAddress(webServiceURI)
' Setup Site web Service proxy
Dim wsUnits As New DC.SL.Services.WebServiceUnit.UnitClient
wsUnits.Endpoint.Address = webServiceAddress
' Add event handler so we can trap for web service completion
AddHandler wsUnits.LoadUnitsCompleted, Sub(s, e)
If e.Error IsNot Nothing Then
tcs.TrySetException(e.Error)
ElseIf e.Cancelled Then
tcs.TrySetCanceled()
Else
tcs.TrySetResult(e.Result)
End If
End Sub
'' Set Busy Status
'BusyStack.Manage(ProcessManager.StackAction.Add, "ReadUnits", Me.IsWorking, Me.IsWorkingMessage)
' Call web service to get Sites the user has access to
wsUnits.LoadUnitsAsync(Me.Client, Me.Site.SiteID, Me.Size.SizeID, Me.RentalType.RentalTypeID, Me.UnitState)
Return tcs.Task
End If
End If
End If
End If
End Function
So here is the final code (abbreviated) that seems to be working to my goals (aka waiting for a Web Service to finish before proceeding).
Public Class UIUnits
Implements INotifyPropertyChanged, IDataErrorInfo
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Async Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
Dim propertyEventHandler As PropertyChangedEventHandler = PropertyChangedEvent
Try
If propertyEventHandler IsNot Nothing Then
RaiseEvent PropertyChanged(Me, e)
Select Case e.PropertyName
Case "Size"
Await ReadUnitsAsync()
DoSomethingElseAfterWebServiceCallCompletes()
Case Else
End Select
End If
Catch ex As Exception
Dim problem As New DC.SL.Tools.Errors(ex)
End Try
End Sub
...
Private _Units As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
Public Property Units() As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
Get
Return _Units
End Get
Set(ByVal value As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
_Units = value
OnPropertyChanged(New PropertyChangedEventArgs("Units"))
End Set
End Property
...
Public Async Function ReadUnitsAsync() As Task(Of Boolean)
Me.Units = Await LoadReadUnitsAsync()
Return True
End Function
...
Public Function LoadReadUnitsAsync() As Task(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
LoadReadUnitsAsync = Nothing
Dim tcs = New TaskCompletionSource(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
' Client is required
If Not Me.Client Is Nothing Then
' User is required
If Not Me.User Is Nothing Then
' Must be a real Client associated
If Me.Client.ClientID > 0 Then
' Only get associated sites IF we don't have any defined
If Not Me.Site Is Nothing Then
' Call the web service relative to where this application is running
Dim webServiceURI As New Uri("../WebServices/Unit.svc", UriKind.RelativeOrAbsolute)
Dim webServiceAddress As New EndpointAddress(webServiceURI)
' Setup web Service proxy
Dim wsUnits As New DC.SL.Services.WebServiceUnit.UnitClient
wsUnits.Endpoint.Address = webServiceAddress
' Add event handler so we can trap for web service completion
AddHandler wsUnits.LoadUnitsCompleted, Sub(s, e)
If e.Error IsNot Nothing Then
tcs.TrySetException(e.Error)
ElseIf e.Cancelled Then
tcs.TrySetCanceled()
Else
tcs.TrySetResult(e.Result)
End If
End Sub
' Call web service
wsUnits.LoadUnitsAsync(Me.Client, Me.Site.SiteID, Me.Size.SizeID, Me.RentalType.RentalTypeID, Me.UnitState)
Return tcs.Task
End If
End If
End If
End If
End Function
In this case it's probably easiest to convert from the lowest level and work your way up. First, you need to define your own TAP-friendly extension methods on your service. VS will generate these for you if you are doing desktop development, but unfortunately it will not do this for Silverlight.
The MSDN docs describe how to wrap EAP (EAP is the pattern that uses *Async methods with matching *Completed events). If you have APM methods, it's even easier to wrap those into TAP (APM is the pattern that uses Begin*/End* method pairs).
Once you have a wrapper, e.g., LoadUnitsTaskAsync, change your method to call that instead of LoadUnitsAsync and Await the result. This will require your ReadUnits method to be Async, so change it to a Task-returning Function (and change its name from ReadUnits to ReadUnitsAsync). Next change all callers of ReadUnitsAsync so they Await its result. Repeat until you reach an actual event handler, which may be Async Sub (do not use Async Sub for any intermediate methods; use Async Function ... As Task instead).
I used this library to achieve async await in my Silverlight 5 project

TCPClient disconnects after several hours

I've created a Windows service that waits for TCPClient connections and relays any messages to all connected clients (except the sender). My code is based on this example.
One client connects when an event is triggered, sends some progress updates and then disconnects. The other clients are front end applications that receive and display the update.
If these clients are left idle for several hours they seem to loose the connection without any error\warning. I cannot find any relevent timouts for idle periods, is there something I am missing?
Service Code:
Protected Overrides Sub OnStart(ByVal args() As String)
_Listener = New TcpListener(IPAddress.Any, 1314)
_Listener.Start()
ListenForClient()
_ConnectionMontior = Task.Factory.StartNew(AddressOf DoMonitorConnections, New MonitorInfo(_Listener, _Connections), TaskCreationOptions.LongRunning)
End Sub
Private Sub ListenForClient()
Dim info As New ConnectionInfo(_Listener)
_Listener.BeginAcceptTcpClient(AddressOf DoAcceptClient, info)
End Sub
Private Sub DoAcceptClient(result As IAsyncResult)
Try
Dim monitorInfo As MonitorInfo = CType(_ConnectionMontior.AsyncState, MonitorInfo)
If monitorInfo.Listener IsNot Nothing AndAlso Not monitorInfo.Cancel Then
Dim info As ConnectionInfo = CType(result.AsyncState, ConnectionInfo)
monitorInfo.Connections.Add(info)
info.AcceptClient(result)
ListenForClient()
info.AwaitData()
End If
Catch ex As Exception
WriteToEventLog("DoAcceptClient: " & ex.Message)
End Try
End Sub
Private Sub DoMonitorConnections()
Try
'Create delegate for updating output display
' Dim doAppendOutput As New Action(Of String)(AddressOf AppendOutput)
'Get MonitorInfo instance from thread-save Task instance
Dim monitorInfo As MonitorInfo = CType(_ConnectionMontior.AsyncState, MonitorInfo)
'Implement client connection processing loop
Do
'Create temporary list for recording closed connections
Dim lostConnections As New List(Of ConnectionInfo)
'Examine each connection for processing
For Each info As ConnectionInfo In monitorInfo.Connections
If info.Client.Connected Then
'Process connected client
If info.DataQueue.Count > 0 Then
'The code in this If-Block should be modified to build 'message' objects
'according to the protocol you defined for your data transmissions.
'This example simply sends all pending message bytes to the output textbox.
'Without a protocol we cannot know what constitutes a complete message, so
'with multiple active clients we could see part of client1's first message,
'then part of a message from client2, followed by the rest of client1's
'first message (assuming client1 sent more than 64 bytes).
Dim messageBytes As New List(Of Byte)
While info.DataQueue.Count > 0
messageBytes.Add(info.DataQueue.Dequeue)
End While
'Relay the message to all clients except the sender
For Each inf As ConnectionInfo In monitorInfo.Connections
If inf.Client.Connected Then
Dim msg As String = info.Client.Client.RemoteEndPoint.ToString & "|" & System.Text.Encoding.ASCII.GetString(messageBytes.ToArray)
If Not inf.Client.Client.RemoteEndPoint.ToString = msg.Split("|")(0) Then
inf.Client.Client.Send(messageBytes.ToArray)
End If
End If
Next
End If
Else
'Record clients no longer connected
lostConnections.Add(info)
End If
Next
'Clean-up any closed client connections
If lostConnections.Count > 0 Then
While lostConnections.Count > 0
monitorInfo.Connections.Remove(lostConnections(0))
lostConnections.RemoveAt(0)
End While
End If
'Throttle loop to avoid wasting CPU time
_ConnectionMontior.Wait(1)
Loop While Not monitorInfo.Cancel
'Close all connections before exiting monitor
For Each info As ConnectionInfo In monitorInfo.Connections
info.Client.Close()
Next
monitorInfo.Connections.Clear()
Catch ex As Exception
WriteToEventLog("DoMonitorConnections" & ex.Message)
End Try
End Sub
Client Code:
_ServerAddress = IPAddress.Parse(ServerIP)
_Connection = New ConnectionInfo(_ServerAddress, 1314, AddressOf InvokeAppendOutput)
_Connection.AwaitData()
ConnectionInfo Class:
Public Class ConnectionInfo
Private _AppendMethod As Action(Of String)
Public ReadOnly Property AppendMethod As Action(Of String)
Get
Return _AppendMethod
End Get
End Property
Private _Client As TcpClient
Public ReadOnly Property Client As TcpClient
Get
Return _Client
End Get
End Property
Private _Stream As NetworkStream
Public ReadOnly Property Stream As NetworkStream
Get
Return _Stream
End Get
End Property
Private _LastReadLength As Integer
Public ReadOnly Property LastReadLength As Integer
Get
Return _LastReadLength
End Get
End Property
Private _Buffer(255) As Byte
Public Sub New(address As IPAddress, port As Integer, append As Action(Of String))
_AppendMethod = append
_Client = New TcpClient
_Client.Connect(address, port)
_Stream = _Client.GetStream
End Sub
Public Sub AwaitData()
_Stream.BeginRead(_Buffer, 0, _Buffer.Length, AddressOf DoReadData, Me)
End Sub
Public Sub Close()
If _Client IsNot Nothing Then _Client.Close()
_Client = Nothing
_Stream = Nothing
End Sub
Private Const MESSAGE_DELIMITER As Char = ControlChars.Cr
Dim sBuilder As New System.Text.StringBuilder
Private Sub DoReadData(result As IAsyncResult)
Dim info As ConnectionInfo = CType(result.AsyncState, ConnectionInfo)
Try
If info._Stream IsNot Nothing AndAlso info._Stream.CanRead Then
info._LastReadLength = info._Stream.EndRead(result)
If info._LastReadLength > 0 Then
Dim message As String = System.Text.Encoding.UTF8.GetString(info._Buffer, 0, info._LastReadLength)
If (message.IndexOf(MESSAGE_DELIMITER) > -1) Then
Dim subMessages() As String = message.Split(MESSAGE_DELIMITER)
sBuilder.Append(subMessages(0))
If Not info._Client.Client.LocalEndPoint.ToString = sBuilder.ToString.Split("|")(0) Then
info._AppendMethod(sBuilder.ToString)
End If
sBuilder = New System.Text.StringBuilder
If subMessages.Length = 2 Then
sBuilder.Append(subMessages(1))
Else
For i As Integer = 1 To subMessages.GetUpperBound(0) - 1
'MessageBox.Show(subMessages(i))
info._AppendMethod(subMessages(i))
Next
sBuilder.Append(subMessages(subMessages.GetUpperBound(0)))
End If
Else
sBuilder.Append(message)
End If
End If
End If
info.AwaitData()
Catch ex As Exception
info._LastReadLength = -1
End Try
End Sub
End Class
TCP does not guarantee that a side not trying to send data can detect a loss of the connection. You should have taken this into account when you designed your application protocol.
What you are seeing is most commonly caused by NAT or stateful firewalls. As a practical matter, if you don't send data at least every ten minutes, you can expect at least some clients to get disconnected. Their NAT devices or stateful firewalls simply forget about the connection. Neither side notices until it tries to send data.
I would suggest creating some kind of dummy message that the server sends to all its clients every five minutes. Basically, this is just some small chunk of data that can be uniquely identified as serving only to keep the connection alive.
Each client responds to the dummy message by sending a dummy message back to the server. If a client doesn't receive a dummy message in ten minutes, it should consider the connection lost, close it, and try to connect again.
The mere act of trying to send the dummy message will cause the server to detect any lost connections, but you should probably also consider as dead any connection to a client that hasn't responded to a dummy message by the time you're ready to send the next one. The client will know a connection is lost when it doesn't receive the dummy message. The exchange of messages will keep the NAT/firewall entry alive.