I have a VB.net app with a DataGridView displaying a list of jobs, retrieved from a SQL query into a DataTable, then a DataView and finally a BindingSource for the DataGridView.DataSource. I have recently added SqlNotificationRequest functionality to the setup so user A is immediately updated when user B performs an action on a job in the list.
I used this MSDN article (http://msdn.microsoft.com/en-US/library/3ht3391b(v=vs.80).aspx) as the basis for my development and it works ok. The problem comes when the user wants to change the parameters of the SQL query which displays the jobs, for example the date displayed. Currently I am creating a new SqlCommand with new Notification but after the user has changed the date a few times (say 30), but no data changes, when the notification timeout occurs I receive the above error when the callback handler tried to EndExecuteReader. My callback handler is below:
Private Sub OnSalesReaderComplete(ByVal asynResult As IAsyncResult)
' You may not interact with the form and its contents
' from a different thread, and this callback procedure
' is all but guaranteed to be running from a different thread
' than the form. Therefore you cannot simply call code that
' updates the UI.
' Instead, you must call the procedure from the form's thread.
' This code will use recursion to switch from the thread pool
' to the UI thread.
If Me.InvokeRequired Then
myStatus.addHistory("OnSalesReaderComplete - Background", "Sub")
Dim switchThreads As New AsyncCallback(AddressOf Me.OnSalesReaderComplete)
Dim args() As Object = {asynResult}
Me.BeginInvoke(switchThreads, args)
Exit Sub
End If
' At this point, this code will run on the UI thread.
Try
myStatus.addHistory("OnSalesReaderComplete - UI", "Sub")
Dim sourceText, rSalesId As String
waitInProgressSales = False
Trace.WriteLine(String.Format("Sales:asynResult.IsCompleted1: {0}", asynResult.IsCompleted.ToString), "SqlNotificationRequest")
Trace.WriteLine(String.Format("Sales:asynResult.CompletedSynchronously: {0}", asynResult.CompletedSynchronously.ToString), "SqlNotificationRequest")
Dim reader As SqlDataReader = DirectCast(asynResult.AsyncState, SqlCommand).EndExecuteReader(asynResult)
Trace.WriteLine(String.Format("Sales:asynResult.IsCompleted2: {0}", asynResult.IsCompleted.ToString), "SqlNotificationRequest")
Do While reader.Read
' Empty queue of messages.
' Application logic could parse
' the queue data to determine why things.
'For i As Integer = 0 To reader.FieldCount - 1
' 'Debug.WriteLine(reader(i).ToString())
' Console.WriteLine(reader(i).ToString)
'Next
Dim bytesQN As SqlBytes = reader.GetSqlBytes(reader.GetOrdinal("message_body"))
Dim rdrXml As XmlReader = New XmlTextReader(bytesQN.Stream)
Do While rdrXml.Read
Select Case rdrXml.NodeType
Case XmlNodeType.Element
Select Case rdrXml.LocalName
Case "QueryNotification"
sourceText = rdrXml.GetAttribute("source")
Case "Message"
rSalesId = rdrXml.ReadElementContentAsString
End Select
End Select
Loop
Loop
reader.Close()
' The user can decide to request
' a new notification by
' checking the check box on the form.
' However, if the user has requested to
' exit, we need to do that instead.
If exitRequestedSales Then
'Me.Close()
commandSales.Notification = Nothing
Else
Select Case sourceText.ToLower
Case "data"
Trace.WriteLine(String.Format("SalesId: {0}, data notification", rSalesId), "SqlNotificationRequest")
Call GetSalesData(True, action.REATTACH)
Case "timeout"
'check timeout is for this user and relates to current wait thread
Select Case salesId = rSalesId
Case True
Trace.WriteLine(String.Format("SalesId: {0}, timeout - current", rSalesId), "SqlNotificationRequest")
Call GetSalesData(True, False)
Case False
Trace.WriteLine(String.Format("SalesId: {0}, timeout - old", rSalesId), "SqlNotificationRequest")
Me.ListenSales()
End Select
End Select
End If
Catch ex As Exception
Call errorHandling(ex, "OnSalesReaderComplete", "Sub")
End Try
End Sub
The problem seems to be where I am only refreshing the data and renewing the notification request (using GetSalesData) when either the notification is due to an update ("data") or the timeout is for the current request. On other notifications, the application calls Me.ListenSales which creates a SQL command "WAITFOR (RECEIVE * FROM [QueueMessage])" and starts the callback listener as per the MSDN article. If I remove the Me.ListenSales line, once a notification is received which is not due to data or the timeout of the current query, the application stops listening for notifications.
I have also posted the same question on MSDN forums (http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataproviders/thread/0f7a636d-0c9b-4b39-b341-6becf13873dc) as I have tried other resolutions without success so require some advice on whether this is possible and if so how.
Related
I have a one threaded application. Its supposed to connect to a local MSMQ queue, and once a message is received it should process it before continuing to listen for additional messages. The messages contain data that is supposed to be inserted into a database table. But before inserting the data, it does a query to see if the item already exists. However, I am thinking that by creating a handler for ReceiveCompleted, that if more than one messages is in the queue that multiple threads are being spawned off. Is that what will happen? If it is then its possible that duplicate data could be in both messages, and my sql query may not see any duplication because the 2nd thread is still working and has not yet inserted its data into the database table.
Dim objQueue As New MessageQueue(ConfigurationManager.AppSettings("myQueuePath"))
AddHandler objQueue.ReceiveCompleted, AddressOf QueueReceived
objQueue.BeginReceive()
Private Sub QueueReceived(source As Object, asyncResult As ReceiveCompletedEventArgs)
Dim mq = DirectCast(source, MessageQueue)
Dim objMessage As Message = Nothing
Try
mq.Formatter = New XmlMessageFormatter(New [String]() {"System.String,mscorlib"})
objMessage = mq.EndReceive(asyncResult.AsyncResult)
Dim strMessage As String = objMessage.Body.ToString()
'Call routine to read data, check for suplicates and then insert into database
ProcessBXRS(objMessage)
Catch ex As Exception
'Do some exception handling
End Try
'Listen for next message
mq.BeginReceive()
End Sub
I guess I have to call BeginReceive() again.
Is there a way to keep a TcpClient connection open constantly? I have an application which lets our users scan a carton, some database updates are performed, and a shipping label is sent to and printed from a wireless hip printer (model of printer is Zebra QLn420) which the user is using.
The application attempts to keep a connection to the wireless printer via the TcpClient connection, and multiple checks are made throughout processing to make sure the connection is good, before sending a generated ZPL to the printer for printing.
We have been having an issue with an occasional label missing, and it seems to be whenever the user stops scanning for a few minutes, then resumes. However, it is a semi-rare occurrence when a label is skipped, and as such is rather hard to reproduce (I haven't been able to replicate it myself, but I have seen it happen out in the warehouse).
I would like to know either if there is a way to make sure that the connection is always open (by "pinging" the device every so often), or if there is a way to get feedback that the data has been received and printed.
This is the code I'm calling to ensure a connection:
Public Function Connect(strIP As String, intPort As Integer) As Boolean
Try
'connect to printer via TcpClient, need ip address and port number
'connects without thread, hangs program for 10-20 seconds if printer is not turned on, replaced with code below to thread the connection and set timeout
For i As Integer = 1 To 2
If Not (client IsNot Nothing AndAlso client.Connected) Then
'uses ClientSocketParameters structure to pass to recursive function ConnectionReturned()
clntSockParams = New ClientSocketParameters
clntSockParams.addrs = strIP
clntSockParams.prt = intPort
'create client and call BeginConnect (attempts to connect on separate thread until TimeoutTime has elapsed)
client = New System.Net.Sockets.TcpClient
client.SendTimeout = 5000
client.ReceiveTimeout = 5000
'setup timer with timeout length and start, if timer goes past intTimeoutLength, the Timeout() function is called which closes everything and leaves client = Nothing
AddHandler TimeoutTime.Elapsed, AddressOf Timeout
TimeoutTime.Interval = intTimeoutLength
TimeoutTime.Start()
client.BeginConnect(strIP, intPort, New AsyncCallback(AddressOf ConnectionReturned), clntSockParams)
'keeps the program from doing anything else until BeginConnect either succeeds or fails (due to connect on separate thread)
Do While TimeoutTime.Enabled
System.Threading.Thread.Sleep(500)
Loop
End If
'if TimeoutTime is elapsed and client is Nothing, connection didn't happen, throw an error
If client Is Nothing Then
blnConnected = False
Else
blnConnected = True
Exit For
End If
Next
Catch ex As Exception
blnConnected = False
End Try
Return blnConnected
End Function
Private Sub ConnectionReturned(ByVal ar As System.IAsyncResult)
'this method is called from the client.BeginConnect line in Connect(), make sure timer is running
If TimeoutTime.Enabled Then
'ensure client is initialized
If client Is Nothing Then client = New System.Net.Sockets.TcpClient
'keep calling ConnectionReturned until client.Connected is true
If client.Connected Then
TimeoutTime.Stop()
Else
Dim actualParameters As ClientSocketParameters = DirectCast(ar.AsyncState, ClientSocketParameters)
client.BeginConnect(actualParameters.addrs, actualParameters.prt, New AsyncCallback(AddressOf ConnectionReturned), clntSockParams)
End If
End If
End Sub
Private Sub Timeout(ByVal sender As Object, ByVal e As EventArgs)
'this method is only called if TimeoutTime elapsed, which means no connection was made. close the client object if needed, set to Nothing, and stop TimeoutTime
If TimeoutTime.Enabled Then
Try
client.Close()
Catch ex As Exception
End Try
client = Nothing
TimeoutTime.Stop()
End If
End Sub
According to this question:
tcp client in vb.net not receiving the entire data response data from server
TcpClient is not always guaranteed to deliver all data to the other end of a connection, so if a more reliable connection method is available, that would be worth a try as well.
Please let me know if more information is needed. Thank you!
Originally I got the code for connecting via this link. I've modified it since because it would hang the application for 10-20 seconds if it took longer to connect. Code here is in C# and I translated to VB:
Send ZPL Commands via TCP/IP in C#
This is the link to the docs for the class:
TcpClient Class
Zebra printers have a timeout setting on TCP that I think has a 3 or 5 minute default. The first thing to do is to turn that timeout off. There will still be other reasons that the printer would disconnect so you will need to handle that as well.
embed this into your program:
! U1 setvar "wlan.ip.timeout.enable" "off"
Make sure you send a CR/LF before and after that line.
if you send a query after your format you can know that the whole format made it to the printer. Something like the following would work:
! U1 getvar "device.uptime"
That's not a sane solution. A sane solution is this: If a label isn't completely sent to the printer because the connection failed, make a new connection and send the label.
I've wrote a program which on startup loads the computer list from Active Directory. This takes about 10 seconds. If the user has started the program with a specific host as parameter, it should be usable immediately.
So to don't interrupt the user I want to load the computer list in a different thread. The problem is that it writes to a variable (the computer list) which is also used in the main thread.
You may think, I could simply use a temporary variable and when its done overwrite the main variable. But I have to keep existing data of the main variable.
'hosts list
Private Shared hosts As New SortedDictionary(Of String, HostEntry)
'Get all computers in Active Directory
'Will run in a extra thread
Private Delegate Sub GetADcomputersDelegate()
Private Sub GetADcomputers()
If Me.InvokeRequired Then
Me.Invoke(New GetADcomputersDelegate(AddressOf GetADcomputers), Nothing)
Else
lblStatusAD.Text = "Getting Computers..."
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End If
End Sub
So when GetADcomputers() runs in its own thread, the main thread is also blocked. I guess because auf the hosts variable.
So what could I change to make the thread do it's work and after that apply the updated computer list without losing data of entries in old hosts list? And all this in a fast and efficient way.
That code is very wrong. If you call that method on a secondary thread then it immediately marshals a call back to the UI thread and does EVERYTHING on the UI thread. What you should be doing is executing all the background work on the secondary thread and then marshalling to the UI thread ONLY to update the UI.
Get rid of that If...Else block and just make the entire body of the method what's current ly in the Else block. Next, identify all the lines that specifically interact with the UI and remove each of those to their own method. You then add If...Else blocks to each of those methods so that only the code that actually touches the UI is executed on the UI thread.
Here's a start:
Private Sub GetADcomputers()
UpdateStatusADLabel("Getting Computers...")
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End Sub
Private Sub UpdateStatusADLabel(text As String)
If lblStatusAD.InvokeRequired Then
lblStatusAD.Invoke(New Action(Of String)(AddressOf UpdateStatusADLabel), text)
Else
lblStatusAD.Text = text
End If
End Sub
I'm not even sure I understand this situation enough to come up with a proper title. I come from a modest understanding of VB6 and having to climb a steep learning curve for VB 2010.
I am trying to create a multi-client server program that will communicate with my Enterprise iPhone app. I found a relatively simple example to build upon here: http://www.strokenine.com/blog/?p=218. I have been able to modify the code enough to make it work with my app, but with one glitch: I can't get access to the controls on the form to add items, even though the method is invoked within the form's class. (I tried this on the original code too, and it does the same thing. I don't know how the author managed to get it to work.)
Here's the code segment in question:
Public Class Server 'The form with the controls is on/in this class.
Dim clients As New Hashtable 'new database (hashtable) to hold the clients
Sub recieved(ByVal msg As String, ByVal client As ConnectedClient)
Dim message() As String = msg.Split("|") 'make an array with elements of the message recieved
Select Case message(0) 'process by the first element in the array
Case "CHAT" 'if it's CHAT
TextBox3.Text &= client.name & " says: " & " " & message(1) & vbNewLine 'add the message to the chatbox
sendallbutone(message(1), client.name) 'this will update all clients with the new message
' and it will not send the message to the client it recieved it from :)
Case "LOGIN" 'A client has connected
clients.Add(client, client.name) 'add the client to our database (a hashtable)
ListBox1.Items.Add(client.name) 'add the client to the listbox to display the new user
End Select
End Sub
Under Case "LOGIN" the code tries to add the login ID to the listbox. It throws an exception: "A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll" The listbox (all controls, for that matter) is in the same class, Server.vb and Server.vb [Design].
The data comes in from another class that is created whenever a client logs on, which raises the event that switches back to the Server class:
Public Class ConnectedClient
Public Event gotmessage(ByVal message As String, ByVal client As ConnectedClient) 'this is raised when we get a message from the client
Public Event disconnected(ByVal client As ConnectedClient) 'this is raised when we get the client disconnects
Sub read(ByVal ar As IAsyncResult) 'this will process all messages being recieved
Try
Dim sr As New StreamReader(cli.GetStream) 'initialize a new streamreader which will read from the client's stream
Dim msg As String = sr.ReadLine() 'create a new variable which will be used to hold the message being read
RaiseEvent gotmessage(msg, Me) 'tell the server a message has been recieved. Me is passed as an argument which represents
' the current client which it has recieved the message from to perform any client specific
' tasks if needed
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'continue reading from the stream
Catch ex As Exception
Try 'if an error occurs in the reading purpose, we will try to read again to see if we still can read
Dim sr As New StreamReader(cli.GetStream) 'initialize a new streamreader which will read from the client's stream
Dim msg As String = sr.ReadLine() 'create a new variable which will be used to hold the message being read
RaiseEvent gotmessage(msg, Me) 'tell the server a message has been recieved. Me is passed as an argument which represents
' the current client which it has recieved the message from to perform any client specific
' tasks if needed
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'continue reading from the stream
Catch ' IF WE STILL CANNOT READ
RaiseEvent disconnected(Me) 'WE CAN ASSUME THE CLIENT HAS DISCONNECTED
End Try
End Try
End Sub
I hope I am making sense with all this. It all seems to bounce back and forth, it seems so convoluted.
I've tried using Me.listbox1 and Server.listbox1 and several other similar structures, but to no avail.
I'm reading a lot about Invoke and Delegates, but would that be necessary if the method and the control are in the same class? Or do I have a fundamental misperception of what a class is?
Many thanks for any help I can get.
Private Delegate Sub UpdateListDelegate(byval itemName as string)
Private Sub UpdateList(byval itemName as string)
If Me.InvokeRequired Then
Me.Invoke(New UpdateListDelegate(AddressOf UpdateList), itemName)
Else
' UpdateList
' add list add code
ListBox1.Items.Add(itemName)
End If
End Sub
Add above, then replace:
ListBox1.Items.Add(client.name)
to
UpdateList(client.name)
Does it work? check the syntax, may have typo as I type it.
This is my sample code run under thread to download file from ftp server. In that, if the user want to stop file download, i tried to abort the thread. If the control in the while loop, its hang up.
How to close the binaryreader and Stream, when reader in the middle of stream
Using response As FtpWebResponse = CType(ftp.GetResponse, FtpWebResponse)
Using input As Stream = response.GetResponseStream()
Using reader As New BinaryReader(input)
Try
Using writer As New BinaryWriter(File.Open(targetFI.FullName, FileMode.Create)) 'output)
Dim buffer(2048) As Byte '= New Byte(2048)
Dim count As Integer = reader.Read(buffer, 0, buffer.Length)
While count <> 0
writer.Write(buffer, 0, count)
count = reader.Read(buffer, 0, buffer.Length)
End While
writer.Close()
End Using
Catch ex As Exception
'catch error and delete file only partially downloaded
targetFI.Delete()
'Throw
ret = False
End Try
reader.Close()
End Using
input.Close()
End Using
response.Close()
End Using
You will need to add "polling" within your While loop to check if a certain condition (in your case, the user wishes to abort the download) is true. If the condition is true, you can exit from the while loop.
For example, you will presumably have a function that is called when the user wishes to stop the download (this is perhaps in response to a Button click on a user interface or some such mechanism).
Using a class level boolean variable (or property), you can simply set this variable to true in response to the user wishing to abort the download, then within your while loop that reads portions of the file from the FTP response stream, you check the value of this variable and if it's true, you simply exit from the while loop:
For example:
Somewhere at the class level, you declare:
Dim blnAbort as Boolean = False
When the user (for example) clicks a button to abort the download:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
blnAbort = True
End Sub
And in your main While Loop from the code in your question, you add:
While count <> 0
' Check the value of the blnAbort variable. If it is true, the user wants to abort, so we exit out of our while loop'
If blnAbort = True Then
Exit While
End If
writer.Write(buffer, 0, count)
count = reader.Read(buffer, 0, buffer.Length)
End While
This is the basic mechanism (polling) by which you should abort a long running process. Of course, you should always ensure that the relevant clean-up code is performed in the event of aborting (in your case, closing the reader and writer, which you are already doing). You may also need to make a call to Application.DoEvents in your while loop if this is being done in the context of a Windows Forms based application, and the user aborting is controlled by some king of GUI interaction. This will ensure that Windows messages generated by (for example) a button click are processed in a timely fashion.