a sendasync mail function, do you see any potential memory leak? - vb.net

I have that code running into a windows service and the service seem to have a memory leak, something is wrong for sure.
After looking at the whole code, I think it might be inside one of these function but I cannot seem to find where it could be.
Anyone could take a look and could let me know if something is wrong?
thanks for any kind of help.
Public Function sendEmail(Optional ByVal msg As String = "") As Boolean
Dim mailSent As Boolean = False
Dim mail As Net.Mail.MailMessage = Nothing
Dim smtp As Net.Mail.SmtpClient = Nothing
Try
mail = New Net.Mail.MailMessage
mail.From = New Net.Mail.MailAddress("myemail#myaddress.com")
mail.Priority = Net.Mail.MailPriority.High
mail.To.Add("1#1.1")
mail.To.Add("2#1.1")
mail.To.Add("3#1.1")
mail.Subject = "test"
mail.Body = msg
Dim stream As IO.MemoryStream = createReport(Of IO.MemoryStream)()
mail.Attachments.Add(New Net.Mail.Attachment(stream, "Report.html", "text/html"))
smtp = New Net.Mail.SmtpClient("my.smtp.server")
AddHandler smtp.SendCompleted, AddressOf SendCompletedCallback
smtp.SendAsync(mail, mail)
mailSent = True
Catch ex As Exception
Debug.Print(ex.Message)
End Try
If mail IsNot Nothing Then
mail = Nothing
End If
If smtp IsNot Nothing Then
smtp = Nothing
End If
Return mailSent
End Function
Private Sub SendCompletedCallback(ByVal sender As Object, ByVal e As ComponentModel.AsyncCompletedEventArgs)
Try
Dim i As Integer
Dim mail As Net.Mail.MailMessage = CType(e.UserState, Net.Mail.MailMessage)
If e.Cancelled Then
Throw New Exception("Send mail got cancelled")
ElseIf e.Error IsNot Nothing Then
Throw e.Error
End If
For i = (mail.Attachments.Count - 1) To 0 Step -1
mail.Attachments(i).Dispose()
Next
mail.Dispose()
RemoveHandler CType(sender, Net.Mail.SmtpClient).SendCompleted, AddressOf SendCompletedCallback
Catch ex As Exception
Debug.Print(ex.Message)
End Try
End Sub
Public Function createReport(Of dataType)() As dataType
Dim result As Object = Nothing
Dim ds As DataSet = Nothing
Dim xmlDoc As Xml.XmlDocument = Nothing
Dim xslTran As Xml.Xsl.XslCompiledTransform = Nothing
Try
Dim i As Integer
ds = New dsEventLog ''dataset
If IO.File.Exists("c:\myxmlfile") Then
ds.Tables(0).ReadXml("c:\myxmlfile")
For i = ds.Tables(0).Rows.Count - 1 To 0 Step -1
If CDate(ds.Tables(0).Rows(i).Item("LocalTime")) < Now.AddDays(-5) Then
ds.Tables(0).Rows.RemoveAt(i)
End If
Next
End If
xmlDoc = New Xml.XmlDataDocument(ds)
xslTran = New Xml.Xsl.XslCompiledTransform
xslTran.Load("c:\myxslfile")
If GetType(dataType) Is GetType(String) Then
'doesn't matter
ElseIf GetType(dataType) Is GetType(IO.MemoryStream) Then
Dim stream = New IO.MemoryStream
Dim sw As IO.StreamWriter = New IO.StreamWriter(stream)
xslTran.Transform(xmlDoc, Nothing, sw)
stream.Position = 0
result = stream
sw = Nothing
stream = Nothing
Else
Throw New Exception("Incorrect ""Of dataType"" used!")
End If
Catch ex As Exception
Debug.Print(ex.Message)
End Try
If ds IsNot Nothing Then
ds.Dispose()
End If
ds = Nothing
xslTran = Nothing
xmlDoc = Nothing
Return CType(result, dataType)
End Function

You should dispose of the memory stream, by calling the Dispose method. You should also dispose of objects of any class that implements IDisposable to release unmanaged memory.
Also, you don't have to set variables to Nothing. This can actually keep objects in memory longer than they have to.
Edit:
If I'm not mistaken the attachments are disposed but not the stream itself...
Also if you look at the example on MSDN, the mail message is disposed in the main code, not in the callback.
Other problem: If the mail cannot be sent, no Dispose is called. If there is an exception, you don't call dispose either. You should put your Dispose calls in finally blocks so that unmanaged resources are disposed in all cases. Or even better, use Using blocks when you can.

Related

How could I go about running access querys on a seperate thread?

I have an app that uses querys for most of its functions, and when the query executes the app ui "freezes". It loads fairly quickly but as it happens many times it gets annoying quick. My app pretty much always uses a class sub to execute the querys. Is there a way to run that class sub on another thread and just return the datatable to the main thread? As im still inexperienced, i never touched threading or the backgroundworker, but from a bit of testing it gives and exception for cross thread operation not valid, and just generally i havent found any good info on how to set up threading for access. This is my sub on the class that executes the query :
Public Sub AddParams(Name As String, value As String, Optional datatype As OleDb.OleDbType = OleDbType.WChar)
Dim NewParam As New OleDbParameter(Name, datatype)
NewParam.Value = value
Params.Add(NewParam)
End Sub
Public Sub ExecuteQuery(query As String)
RecordCount = 0
Exception = ""
Try
DBconnection.Open()
DBcmd = New OleDbCommand(query, DBconnection)
Params.ForEach(Sub(p) DBcmd.Parameters.Add(p))
Params.Clear()
DBdatatable = New DataTable
DBdataadapter = New OleDbDataAdapter(DBcmd)
RecordCount = DBdataadapter.Fill(DBdatatable)
Catch ex As Exception
Exception = ex.Message
End Try
If DBconnection.State = ConnectionState.Open Then DBconnection.Close()
End Sub
Thank you.
Edit:
After some trying i still cant figure it out so i thought id just post my code after Jimi's suggestion to modify it to be async:
Public Async Function ExecuteQueryAsync(query As String) As Task(Of DataTable)
Dim dt As DataTable = New DataTable
RecordCount = 0
Exception = ""
Try
Using dbconn As New OleDbConnection([my connectionstring])
DBcmd = New OleDbCommand(query, dbconn)
Params.ForEach(Sub(p) DBcmd.Parameters.Add(p))
Params.Clear()
Await dbconn.OpenAsync
dt.Load(Await DBcmd.ExecuteReaderAsync)
End Using
Catch ex As Exception
Exception = ex.Message
End Try
If DBconnection.State = ConnectionState.Open Then DBconnection.Close()
Return dt
End Function
And here is the code that uses the query in a usercontrol:
Private Async Sub Search(Nev As String)
Access.AddParams("#nev", "%" & Nev & "%")
dgv.DataSource = Await Access.ExecuteQueryAsync("SELECT * from DT_ugyfelek WHERE Név LIKE #nev")
Dim f As notification = New notification
If NotEmpty(Access.Exception) Then f.SetAlert(Access.Exception, notification.AlertTypeEnum.Error) : Exit Sub
dgv.ColumnHeadersHeight = 75
dgv.Columns(0).Visible = False
dgv.Columns(1).Width = 230
dgv.Columns(1).HeaderText = "Név"
'i also set 10 other headertexts here
End Sub
Private Async Sub search_btn_Click(sender As Object, e As EventArgs) Handles search_btn.Click
Search(search_txtbx.Text)
End Sub
By the way it loads about 20k rows. Honestly all my intention with this is to have a loading gif or something like that so the user would know that something is happening.

Get File Size on FTP Server and put it on a Label

I'm trying to get the size of a file that is hosted on a FTP Server and put it in a Label while the `BackgroundWorker works in the background.
I'm using "Try" to get the value, however the value is caught on the first attempt. After downloading, if I press to try to get it again then it works.
Note: The progress bar also does not work on the first try.
Image
What I have tried:
Private Sub BWorkerD_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BWorkerD.DoWork
Dim buffer(1023) As Byte
Dim bytesIn As Integer
Dim totalBytesIn As Integer
Dim output As IO.Stream
Dim flLength As Integer
''TRY TO GET FILE SIZE''
Try
Dim FTPRequest As FtpWebRequest = DirectCast(WebRequest.Create(txtFilePathD.Text), FtpWebRequest)
FTPRequest.Credentials = New NetworkCredential(txtFTPUsernameD.Text, txtFTPPasswordD.Text)
FTPRequest.Method = Net.WebRequestMethods.Ftp.GetFileSize
flLength = CInt(FTPRequest.GetResponse.ContentLength)
lblFileSizeD.Text = flLength & " bytes"
Catch ex As Exception
End Try
Try
Dim FTPRequest As FtpWebRequest = DirectCast(WebRequest.Create(txtFilePathD.Text), FtpWebRequest)
FTPRequest.Credentials = New NetworkCredential(txtFTPUsernameD.Text, txtFTPPasswordD.Text)
FTPRequest.Method = WebRequestMethods.Ftp.DownloadFile
Dim stream As IO.Stream = FTPRequest.GetResponse.GetResponseStream
Dim OutputFilePath As String = txtSavePathD.Text & "\" & IO.Path.GetFileName(txtFilePathD.Text)
output = IO.File.Create(OutputFilePath)
bytesIn = 1
Do Until bytesIn < 1
bytesIn = stream.Read(buffer, 0, 1024)
If bytesIn > 0 Then
output.Write(buffer, 0, bytesIn)
totalBytesIn += bytesIn
lblDownloadedBytesD.Text = totalBytesIn.ToString & " bytes"
If flLength > 0 Then
Dim perc As Integer = (totalBytesIn / flLength) * 100
BWorkerD.ReportProgress(perc)
End If
End If
Loop
output.Close()
stream.Close()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
''UPDATE EVERY PROGRESS - DONT WORK ON FIRST TRY''
Private Sub BWorkerD_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BWorkerD.ProgressChanged
pBarD.Value = e.ProgressPercentage
lblPercentD.Text = e.ProgressPercentage & " %"
End Sub
The main problems (set Option Strict On to find more):
You can't access the UI objects from a thread different than the UI Thread.
The error you receive is:
Cross-thread operation not valid:Control lblFileSizeD accessed from
a thread other than the thread it was created on
Then, the same error for lblDownloadedBytesD.
Also, you are eating up your Error messages using an empty handler with:
Catch ex As Exception
End Try
This nullifies any handling, because there's none. You are simply letting the code run past it without taking any action. The handlers are there to, well, handle the errors, not to let them go unchecked.
When you need to access and update some UI component property, use the BackGroundWorker ReportProgress() method. This method has an overload that accepts a parameter of type Object. Meaning, you can feed it anything. This Object will be the e.UserState property in the ReportProgress ProgressChangedEventArgs class.
The .RunWorkerAsync() method also accepts an Object parameter. This Object will become the e.Argument property of the BackgroundWorker.DoWork Event. This gives some flexibility in relation to the parameters you can actually pass to your BackGroundWorker.
One more problem: the Ftp Download procedure does not support cancellation. When run, a user can't stop it.
Last problem: as reported in the documentation, you should never reference the BackGroundWorker object you instantiated in your UI thread (the Form) in its DoWork event. Use the sender object and cast it to the BackGroundWorker class.
In this example, all the UI references are delegated to a Class object that is passed to the DoWork event through the RunWorkerAsync(Object) method (using the e.Argument property).
The Class object is updated with progress details and then fed to the ReportProgress(Int32, Object) method, which runs in the original Synchronization Context (the UI thread, where the RunWorkerAsync method is called).
The UI can be updated safely. No cross-thread operations can occur.
A cancellation method is also implemented. This allows to abort the download procedure and to delete a partial downloaded file, if one is created.
The error handling is minimal, but this is something you need to integrate with your own tools.
(I've used the same names for the UI Controls, it should be easier to test.)
Imports System.ComponentModel
Imports System.Globalization
Imports System.IO
Imports System.Net
Imports System.Net.Security
Imports System.Security.Cryptography.X509Certificates
Public Class frmBGWorkerDownload
Friend WithEvents BWorkerD As BackgroundWorker
Public Sub New()
InitializeComponent()
BWorkerD = New BackgroundWorker()
BWorkerD.WorkerReportsProgress = True
BWorkerD.WorkerSupportsCancellation = True
AddHandler BWorkerD.DoWork, AddressOf BWorkerD_DoWork
AddHandler BWorkerD.ProgressChanged, AddressOf BWorkerD_ProgressChanged
AddHandler BWorkerD.RunWorkerCompleted, AddressOf BWorkerD_RunWorkerCompleted
BWorkerD.RunWorkerAsync(BGWorkerObj)
End Sub
Private Class BGWorkerObject
Public Property UserName As String
Public Property Password As String
Public Property ResourceURI As String
Public Property FilePath As String
Public Property FileLength As Long
Public Property DownloadedBytes As Long
Public Property BytesToDownload As Long
End Class
Private Sub btnDownload_Click(sender As Object, e As EventArgs) Handles btnDownload.Click
pBarD.Value = 0
Dim BGWorkerObj As BGWorkerObject = New BGWorkerObject With {
.ResourceURI = txtFilePathD.Text,
.FilePath = Path.Combine(txtSavePathD.Text, Path.GetFileName(txtFilePathD.Text)),
.UserName = txtFTPUsernameD.Text,
.Password = txtFTPPasswordD.Text
}
End Sub
Private Sub BWorkerD_DoWork(sender As Object, e As DoWorkEventArgs)
Dim BGW As BackgroundWorker = TryCast(sender, BackgroundWorker)
Dim BGWorkerObj As BGWorkerObject = TryCast(e.Argument, BGWorkerObject)
Dim FTPRequest As FtpWebRequest
Dim BufferSize As Integer = 131072
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
ServicePointManager.ServerCertificateValidationCallback =
Function(snd As Object, Cert As X509Certificate, Chain As X509Chain, Err As SslPolicyErrors)
Return True
End Function
FTPRequest = DirectCast(WebRequest.Create(BGWorkerObj.ResourceURI), FtpWebRequest)
FTPRequest.Credentials = New NetworkCredential(BGWorkerObj.UserName, BGWorkerObj.Password)
'FTPRequest.Method = WebRequestMethods.Ftp.GetFileSize
'----------------------- UPDATE ------------------------
FTPRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails
'--------------------- END UPDATE ------------------------
FTPRequest.EnableSsl = True
'----------------------- UPDATE ------------------------
Using FtpResponse As WebResponse = FTPRequest.GetResponse,
DirListStream As Stream = FtpResponse.GetResponseStream(),
listReader As StreamReader = New StreamReader(DirListStream)
While Not listReader.EndOfStream
Dim DirContent As String = listReader.ReadLine()
If DirContent.Contains(Path.GetFileNameWithoutExtension(BGWorkerObj.ResourceURI)) Then
BGWorkerObj.FileLength = Convert.ToInt64(DirContent.Split(New String() {" "}, StringSplitOptions.RemoveEmptyEntries)(4))
BGW.ReportProgress(0, BGWorkerObj)
Exit While
End If
End While
End Using
'----------------------- END UPDATE ------------------------
'Using FtpResponse As WebResponse = FTPRequest.GetResponse
' BGWorkerObj.FileLength = Convert.ToInt64(FtpResponse.ContentLength)
' BGW.ReportProgress(0, BGWorkerObj)
'End Using
If BGW.CancellationPending Then e.Cancel = True
Try
FTPRequest = CType(WebRequest.Create(BGWorkerObj.ResourceURI), FtpWebRequest)
FTPRequest.EnableSsl = True
FTPRequest.Credentials = New NetworkCredential(BGWorkerObj.UserName, BGWorkerObj.Password)
FTPRequest.Method = WebRequestMethods.Ftp.DownloadFile
Using Response As FtpWebResponse = DirectCast(FTPRequest.GetResponse, FtpWebResponse)
If Response.StatusCode > 299 Then
e.Result = 0
Throw New Exception("The Ftp Server rejected the request. StatusCode: " &
Response.StatusCode.ToString(),
New InvalidOperationException(Response.StatusCode.ToString()))
Exit Sub
End If
Using stream = Response.GetResponseStream(),
fileStream As FileStream = File.Create(BGWorkerObj.FilePath)
Dim read As Integer
Dim buffer As Byte() = New Byte(BufferSize - 1) {}
Do
read = stream.Read(buffer, 0, buffer.Length)
fileStream.Write(buffer, 0, read)
BGWorkerObj.DownloadedBytes += read
BGWorkerObj.BytesToDownload = BGWorkerObj.FileLength - BGWorkerObj.DownloadedBytes
If BGW.CancellationPending Then
e.Cancel = True
Exit Do
Else
BGW.ReportProgress(CInt((CSng(BGWorkerObj.DownloadedBytes) / BGWorkerObj.FileLength) * 100), BGWorkerObj)
End If
Loop While read > 0
End Using
End Using
Catch ex As Exception
If e.Cancel = False Then Throw
Finally
If e.Cancel = True Then
If File.Exists(BGWorkerObj.FilePath) Then
File.Delete(BGWorkerObj.FilePath)
End If
End If
End Try
End Sub
Private Sub BWorkerD_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
pBarD.Value = e.ProgressPercentage
lblPercentD.Text = e.ProgressPercentage.ToString() & " %"
If lblFileSizeD.Text.Length = 0 Then
lblFileSizeD.Text = CType(e.UserState, BGWorkerObject).FileLength.ToString("N0", CultureInfo.CurrentUICulture.NumberFormat)
End If
lblDownloadedBytesD.Text = CType(e.UserState, BGWorkerObject).DownloadedBytes.ToString("N0", CultureInfo.CurrentUICulture.NumberFormat)
If e.ProgressPercentage <= 15 Then
lblDownloadedBytesD.ForeColor = Color.Red
ElseIf e.ProgressPercentage <= 66 Then
lblDownloadedBytesD.ForeColor = Color.Orange
Else
lblDownloadedBytesD.ForeColor = Color.LightGreen
End If
End Sub
Private Sub BWorkerD_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
Dim DownloadAborted As Boolean = False
If e.Error IsNot Nothing Then
DownloadAborted = True
lblDownloadedBytesD.ForeColor = Color.Red
lblDownloadedBytesD.Text = "Error!"
ElseIf e.Cancelled Then
DownloadAborted = True
lblDownloadedBytesD.ForeColor = Color.Yellow
lblDownloadedBytesD.Text = "Cancelled!"
pBarD.Value = 0
lblPercentD.Text = "0%"
Else
lblDownloadedBytesD.ForeColor = Color.LightGreen
lblDownloadedBytesD.Text = "Download completed"
End If
End Sub
Private Sub btnAbortDownload_Click(sender As Object, e As EventArgs) Handles btnAbortDownload.Click
BWorkerD.CancelAsync()
End Sub
End Class
A visual result of the operation described:
A PasteBin of the Form's Designer + Code

Set IP and DNS to use DHCP from WMI in VB.net

I'm trying to interact with WMI through a vb.net program in order to make any machine that runs this program pull the IP settings and DNS server settings from DHCP for all network adapters with an IP.
The code I presently have works for DHCP without issue, but does not change DNS settings. The program compiles and executes without issue, but the DNS settings are not changing to be fetched automatically from DHCP.
Dim objMC As ManagementClass = New ManagementClass("Win32_NetworkAdapterConfiguration")
Dim objMOC As ManagementObjectCollection = objMC.GetInstances()
For Each objMO As ManagementObject In objMOC
If (Not CBool(objMO("IPEnabled"))) Then
Continue For
End If
Try
Dim objNewIP As ManagementBaseObject = Nothing
Dim objSetIP As ManagementBaseObject = Nothing
Dim objNewDNS As ManagementBaseObject = Nothing
Dim objSetDNS As ManagementBaseObject = Nothing
objNewIP = objMO.GetMethodParameters("EnableDHCP")
objSetIP = objMO.InvokeMethod("EnableDHCP", Nothing, Nothing)
objNewDNS = objMO.GetMethodParameters("SetDNSServerSearchOrder")
objSetDNS = objMO.InvokeMethod("SetDNSServerSearchOrder", Nothing, Nothing)
Catch ex As Exception
MessageBox.Show("Settings unchanged : " & ex.Message)
End Try
Next objMO
I'm so close to getting this solved, I just need help to figure out this last step.
You annoyingly have to do it through the registry, they didn't add WMI methods for it. Specifically (taken from here: https://gallery.technet.microsoft.com/7b1cec46-bdb8-4afc-b240-9789eefce6de) you need to set this key to null.
"HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\"
Below is your code with the necessary new sub inserted in
Const conKeyPath = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces"
Public Sub Test()
Dim objMC As ManagementClass = New ManagementClass("Win32_NetworkAdapterConfiguration")
Dim objMOC As ManagementObjectCollection = objMC.GetInstances()
For Each objMO As ManagementObject In objMOC
If (Not CBool(objMO("IPEnabled"))) Then
Continue For
End If
Try
Dim objNewIP As ManagementBaseObject = Nothing
Dim objSetIP As ManagementBaseObject = Nothing
Dim objNewDNS As ManagementBaseObject = Nothing
Dim objSetDNS As ManagementBaseObject = Nothing
objNewIP = objMO.GetMethodParameters("EnableDHCP")
objSetIP = objMO.InvokeMethod("EnableDHCP", Nothing, Nothing)
objNewDNS = objMO.GetMethodParameters("SetDNSServerSearchOrder")
objSetDNS = objMO.InvokeMethod("SetDNSServerSearchOrder", Nothing, Nothing)
SetDNSAutomatically(objMO.GetPropertyValue("settingID"))
Catch ex As Exception
MessageBox.Show("Settings unchanged : " & ex.Message)
End Try
Next objMO
End Sub
Private Sub SetDNSAutomatically(ByVal settingID As String)
If settingID = String.Empty Then
Throw New ArgumentNullException("settingID")
End If
Dim _adapterKeyPath = String.Format("{0}\{1}", conKeyPath, settingID)
My.Computer.Registry.SetValue(_adapterKeyPath, "NameServer", String.Empty)
End Sub

SQL dependency on relational tables

I have two related tables (tbVehicles and tbVehiclesDoc) I am running the below sql dependency but it is firring many time and my program freezes. please assist.
I want the program to update these tables when another user modifies them.
my Code:
Public Sub getdata()
Try
If tbdataset.Tables.Contains("tbVehicles") Then
If tbdataset.Tables("tbVehiclesDoc") IsNot Nothing Then
For f As Integer = tbdataset.Tables("tbVehiclesDoc").ChildRelations.Count - 1 To 0 Step -1
tbdataset.Tables("tbVehiclesDoc").ChildRelations(f).ChildTable.Constraints.Remove(tbdataset.Tables("tbVehiclesDoc").ChildRelations(f).RelationName)
tbdataset.Tables("tbVehiclesDoc").ChildRelations.RemoveAt(f)
Next
tbdataset.Tables("tbVehiclesDoc").ChildRelations.Clear()
tbdataset.Tables("tbVehiclesDoc").ParentRelations.Clear()
tbdataset.Tables("tbVehiclesDoc").Constraints.Clear()
tbdataset.Tables.Remove("tbVehiclesDoc")
End If
tbdataset.Tables.Remove("tbVehicles")
tbdataset.EnforceConstraints = True
End If
gridVehicles.DataSource = Nothing
Catch ex As Exception
MsgBox(ex.ToString)
' Exit Sub
End Try
command.Notification = Nothing
Dim dependency As New SqlDependency(command)
AddHandler dependency.OnChange, AddressOf dependency_OnChange
Dim adapter As New SqlDataAdapter(command)
adapter.Fill(tbdataset, "tbVehicles")
'----
End Sub
Private Sub toupdate()
If CanRequestNotifications() Then
If connection Is Nothing Then
connection = New SqlConnection(GetConnectionString())
connection.Open()
End If
If connection.State = ConnectionState.Closed Then
connection.Open()
End If
If command Is Nothing Then
command = New SqlCommand(GETSQL(), connection)
End If
End If
getdata()
End Sub
Private Function GETSQL() As String
Return "Select Rtrim(VehcID) as VehcID,RTrim(VehcModel), Rtrim(VehcRegNo) as VehcRegNo, Rtrim(VehcFourWheel), Rtrim(VehcCondition), Rtrim(VehcLastKM),Rtrim(VehcBranch), Rtrim(VehcDepartment), Rtrim(VehcDriver), Rtrim(VehcRemarks), VehcPic from dbo.tbVehicles"
End Function
Private Sub dependency_OnChange(ByVal sender As Object, ByVal e As SqlNotificationEventArgs)
' This event will occur on a thread pool thread.
' It is illegal to update the UI from a worker thread
' The following code checks to see if it is safe
' update the UI.
Dim i As ISynchronizeInvoke = CType(Me, ISynchronizeInvoke)
' If InvokeRequired returns True, the code
' is executing on a worker thread.
If i.InvokeRequired Then
' Create a delegate to perform the thread switch
Dim tempDelegate As New OnChangeEventHandler( _
AddressOf dependency_OnChange)
Dim args() As Object = {sender, e}
' Marshal the data from the worker thread
' to the UI thread.
i.BeginInvoke(tempDelegate, args)
Return
End If
' Remove the handler since it's only good
' for a single notification
Dim dependency As SqlDependency = _
CType(sender, SqlDependency)
RemoveHandler dependency.OnChange, _
AddressOf dependency_OnChange
' Reload the dataset that's bound to the grid.
getdata()
End Sub

Multithreading A Function in VB.Net

I am trying to multi thread my application so as it is visible while it is executing the process, this is what I have so far:
Private Sub SendPOST(ByVal URL As String)
Try
Dim DataBytes As Byte() = Encoding.ASCII.GetBytes("")
Dim Request As HttpWebRequest = TryCast(WebRequest.Create(URL.Trim & "/webdav/"), HttpWebRequest)
Request.Method = "POST"
Request.ContentType = "application/x-www-form-urlencoded"
Request.ContentLength = DataBytes.Length
Request.Timeout = 1000
Request.ReadWriteTimeout = 1000
Dim PostData As Stream = Request.GetRequestStream()
PostData.Write(DataBytes, 0, DataBytes.Length)
Dim Response As WebResponse = Request.GetResponse()
Dim ResponseStream As Stream = Response.GetResponseStream()
Dim StreamReader As New IO.StreamReader(ResponseStream)
Dim Text As String = StreamReader.ReadToEnd()
PostData.Close()
Catch ex As Exception
If ex.ToString.Contains("401") Then
TextBox2.Text = TextBox2.Text & URL & "/webdav/" & vbNewLine
End If
End Try
End Sub
Public Sub G0()
Dim siteSplit() As String = TextBox1.Text.Split(vbNewLine)
For i = 0 To siteSplit.Count - 1
Try
If siteSplit(i).Contains("http://") Then
SendPOST(siteSplit(i).Trim)
Else
SendPOST("http://" & siteSplit(i).Trim)
End If
Catch ex As Exception
End Try
Next
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim t As Thread
t = New Thread(AddressOf Me.G0)
t.Start()
End Sub
However, the 'G0' sub code is not being executed at all, and I need to multi thread the 'SendPOST' as that is what slows the application.
Catch ex As Exception
End Try
A very effective way to stop .NET from telling you what you did wrong. Not knowing why it doesn't work is however the inevitable outcome.
Delete that.
Public Class Form1
'This just shows some concepts of threading.
'it isn't intended to do anything
'requires a Button, and two Labels
'
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button1.Click
'starts / stops a test thread
'isRun = 0 no thread running, start one
'isRun = 1 thread running, stop it
If Threading.Interlocked.Read(isRun) = 0L Then
'start thread
Threading.Interlocked.Increment(isRun)
t = New Threading.Thread(AddressOf showTime)
'simple threading app - display time about twice per second
t.IsBackground = True 'from a background thread
t.Start()
Else
'stop thread
Threading.Interlocked.Exchange(isRun, 0L)
t.Join() 'wait for thread to end
Threading.Monitor.Enter(listLock)
intervalList.Clear() 'clear the list
Threading.Monitor.Exit(listLock)
Label1.Text = "Stop"
Label2.Text = ""
End If
End Sub
Dim t As Threading.Thread
Dim intervalList As New List(Of Double)
Dim listLock As New Object
Dim isRun As Long = 0L
Private Sub showTime()
Dim dlgt As New UpdLblDel(AddressOf UpdateLabel) 'delegate for UI access
Dim lastDateTime As DateTime = Nothing
Do
Dim d As DateTime = DateTime.Now
If lastDateTime <> Nothing Then
'record difference of times - check sleep interval
Threading.Monitor.Enter(listLock)
intervalList.Add((d - lastDateTime).TotalMilliseconds)
Threading.Monitor.Exit(listLock)
End If
lastDateTime = DateTime.Now
dlgt.BeginInvoke(d, Nothing, Nothing) 'update the UI - note immediate return
Threading.Thread.Sleep(500) 'sleep for approx. 500 ms.
Loop While Threading.Interlocked.Read(isRun) = 1L
End Sub
Delegate Sub UpdLblDel(ByVal theTime As Object)
Private Sub UpdateLabel(ByVal theTime As Object)
If Threading.Interlocked.Read(isRun) = 1L Then
If Label1.InvokeRequired Then 'prevent cross-thread errors
Label1.BeginInvoke(New UpdLblDel(AddressOf UpdateLabel), theTime)
Exit Sub
Else
Label1.Text = CType(theTime, DateTime).ToString("HH:mm:ss.f") 'show the time from the background thread
End If
If Threading.Interlocked.Read(intervalList.Count) >= 10L Then
'take average
Threading.Monitor.Enter(listLock)
Dim avg As Double = intervalList.Sum / intervalList.Count 'sum all of the intervals / count
intervalList.Clear() 'clear the list
intervalList.Add(avg) 'forward the average
Label2.Text = avg.ToString("n2") 'show average
Threading.Monitor.Exit(listLock)
End If
End If
End Sub
End Class
You have to wrap the method that accesses the UI component in a delegate (it doesn't have to be a named delegate; it can be anonymous or an Action or Func), and then pass that to Me.Invoke, as others have alluded to.
In this example, I'm wrapping the split functionality in a lambda, and assigning that lambda to a variable of type Func(Of String()). I then pass that variable to Me.Invoke.
Public Sub G0()
Dim siteSplitFunc As Func(Of String()) = Function() _
TextBox1.Text.Split(vbNewLine.ToCharArray())
Dim siteSplit As String() = CType(Me.Invoke(siteSplitFunc), String())
For i = 0 To siteSplit.Count - 1
Try
If siteSplit(i).Contains("http://") Then
SendPOST(siteSplit(i).Trim)
Else
SendPOST("http://" & siteSplit(i).Trim)
End If
Catch ex As Exception
'Do something useful
End Try
Next
End Sub
You cannot access UI object directly from a thread.
When you want to read/write a textbox, you have to do it in the UI thread. This can be done by using Invoke. Or better yet, send/receive the information with parameters.
Here's a Delegate and a matching method. You call the method to update the textbox and it figures out if it should proxy the method for you by basically asking form if its on the same thread:
Private Delegate Sub UpdateTextBoxDelegate(ByVal text As String)
Private Sub UpdateTextBox(ByVal text As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), text)
Else
TextBox2.Text &= text
End If
End Sub
To use it, just change your catch statement to:
If ex.ToString.Contains("401") Then
UpdateTextBox(URL & "/webdav/" & vbNewLine)
End If