I have made several VB apps that communicate using Windows Remoting, but this is the first time I ran into this problem.
There are two programs in my project, a client and a server. The server program has a queue. The client program adds items to the queue by calling a server method. But when the server program checks the queue, it is empty.
Furthermore, the server program instantiates several classes, but when the client tries to use them, it finds that they are Nothing. So this is a general problem not just an issue with the queue per se.
I have had experience with threading problems in the past, so I assumed that this was some kind of threading problem. I tried using a delegate function, but that did not help.
Here is a snippet of code to illustrate where the problem appeared. My apologies for not knowing how to make it properly formatted, this is my first attempt.
' ====================================================================
' this class is instantiated on the server at startup time
Public Class CPAutoDispatcher
' EXAMPLE #1
Public mWLQueue As New Collection
' This function is called from the remote client using Windows Remoting
Public Function SendWorkList(ByVal theList As String) As Boolean
Dim objWL As New AutoWorkList
If Not parseWorkList(theList, objWL) Then Exit Function
Call mWLQueue.Add(objWL)
SendWorkList = True
End Function
' This function is called from the server
Public Sub Tick()
If mWLQueue.Count = 0 Then Exit Sub ' <-- THIS ALWAYS EXITS!
Dim objWL As AutoWorkList = mWLQueue.Item(1)
Call mWLQueue.Remove(1)
' ... do something with objWL ...
End Sub
' EXAMPLE #2
Private mServerReports() As CPAutoServerReport
Private mDelGNR As DEL_GetNewReport = AddressOf getNewReportDelegate
' This function is called from the server
Public Function ProcessMessage(objSR As CPAutoServerReport) As Boolean
If mServerReports Is Nothing Then
ReDim mServerReports(0)
mServerReports(0) = objSR
Else
' ... do something else ...
End If
End Function
' This function is called from the remote client using Windows Remoting
Public Function GetNewReport() As CPAutoServerReport
GetNewReport = mDelGNR.Invoke
End Function
Private Function getNewReportDelegate() As CPAutoServerReport
If mServerReports Is Nothing Then Exit Function ' <-- THIS ALWAYS EXITS!
' ... do something with mServerReports ...
End Function
End Class
' ================================================================
Example #1: Similar code in other projects works, so I expected mWLQueue and mServerReports to be reachable by both the server and the client. But they are not. The client finds mWLQueue, but it is empty.
Example #2: If this was simply a threading issue, I would expect the delegate to make thing right. But it does not. The client finds that mServerReports Is Nothing, even after it has been set.
My code is behaving as if there are TWO instances of my CPAutoDispatcher class, one for the server thread, and another for the client thread (the remoting calls). But there is only one global variable, which is referenced by both threads.
I am baffled by this situation. Am I missing something that should be obvious?
The ultimate cause of my problems was the presence of duplicate declarations, which did not cause a compile error, and so went unnoticed. When I removed the extra declarations, all of the weird behavior went away.
I suspect that somehow, an instance of the alternate class was instantiated automatically, and being uninitialized, had an empty queue and references that were Nothing. But I don't understand how that came about.
Related
I know this appears to be a rather common topic and should have been resolved from earlier posts. But what I am experiencing still does not seem to have a solution online:
I have a form called ExpenseEntry in which there is a sub procedure called Public Sub OpenVoucher.
I want to call this sub from another form for which I use the following code:
Dim ExpForm As New ExpenseEntry
ExpForm.Show()
ExpForm.OpenVoucher()
While this works well enough, the problem is everytime I click the button, a new window of ExpenseEntry is launched. As per how I have designed the application, repeat windows is not permissible and only one window should be available at a time.
I have tried various methods to restrict more than one form such as by using a variable to control the form but that gives rise to other issues.
If I use Application.OpenForms but still does not resolve the issue.
I have earlier queried in this regard in the following link:
Textbox not refreshing
I am using VB.NET 2019 which does not allow the launch of default instance of a form like Form.Show. I know this is bad practice but it was easier to manage with that till VB.NET 2017.
Now by creating a form variable and launching that creates an infinite loop where I cannot have just one instance of a form running on a single thread.
The really simple way to handle this is to use the default instance of the form type. In VB, since 2005, each form type has a default instance that you can access via the type name. Read here for more info. In your case, you can do this:
'Display the form if it is not already displayed.
ExpenseEntry.Show()
'Activate the form if it is already displayed.
ExpenseEntry.Activate()
'Do the deed.
ExpenseEntry.OpenVouncher()
That said, default instances are a bit dodgy. They do enable beginners to access forms from anywhere in their project under certain circumstances but they also have limitations that can cause issues. Most importantly though, they help to prevent you learning proper OOP by treating forms differently to other types. If you want to do this the way a proper developer would then simply declare a variable to refer to the current instance of the form:
Private expenseEntryDialogue As ExpenseEntry
When it's time to use the form, you simply check whether that variable refers to a usable instance and use it if it does, otherwise create a new one:
If expenseEntryDialogue Is Nothing OrElse expenseEntryDialogue.IsDisposed Then
expenseEntryDialogue = New ExpenseEntry
End If
expenseEntryDialogue.Show()
expenseEntryDialogue.Activate()
expenseEntryDialogue.OpenVoucher()
A third option would be to implement your own singleton, i.e. a type that can only ever have a single instance. You probably wouldn't do that in VB, given that the default instance is basically a thread-specific singleton and does more automatically but, if you wanted to, you could do this:
Public Class ExpenseEntry
Private Shared _instance As ExpenseEntry
'The one and only instance of the type.
Public Shared ReadOnly Property Instance As ExpenseEntry
Get
If _instance Is Nothing OrElse _instance.IsDisposed Then
_instance = New ExpenseEntry
End If
Return _instance
End Get
End Property
'The constructor is private to prevent external instantiation.
Private Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
End Class
and then this:
ExpenseEntry.Instance.Show()
ExpenseEntry.Instance.Activate()
ExpenseEntry.Instance.OpenVoucher()
I have a bug in my application which is the same as here which this person was running into the same problem. My application is multi threaded where the worker thread is updating the Waveformgraph on the UI. I believe that is where my problem is and why, periodically, and on occassion I get a big red X in at least one of my waveformgraph objects when running the application. From reading and research, I need to use an Invoke or BeginInvoke method? Can someone please explain better and provide a sample code that is relevant to my code? The samples that I've found so far still have me hazy on how I need to do this or what I need to do. Thank you for your help.
This code is on the swScopeOnOff click event, main thread.
thread2 = New System.Threading.Thread(AddressOf dataAcquiring)
thread2.Start()
This code is in dataAcquiring Sub
Public Sub dataAcquiring()
'While Scope switch is on, stream each Ai channel's data continuously to its respective WaveForm graph
Do While swScopeOnOff.Value = True
data = reader.ReadWaveform(readRate)
i = 0
For Each WaveformGraph In WFGS
WaveformGraph.PlotWaveformAppend(data(i)) 'This line is updating the UI's waveform graphs
i += 1
Next
i = 0
Loop
End Sub
Proper, thread-safe invocation is actually not as hard as one might think (not even for thread-safe events, but that's irrelevant for this question).
I would recommend you to use the normal Invoke method, such as Me.Invoke() (where Me is the current form, if not, use Form1 or whatever it's called instead). Using BeginInvoke() may be asynchronous but it stacks memory usage and can cause memory leaks if EndInvoke() is not called correctly.
If you target .NET 4.0 or higher you can simply do like this:
Me.Invoke(Sub() WaveformGraph.PlotWaveformAppend(data(i)))
However if you target .NET 3.5 or lower it requires a few more lines of code.
'Outside your Sub.
Delegate Sub WaveformAppendDelegate(ByRef WaveformGraph, ByRef data)
'Create a new sub.
Public Sub AppendData(ByRef WaveformGraph, ByRef data)
WaveformGraph.PlotWaveformAppend(data)
End Sub
'Inside your sub, when you're going to invoke.
Me.Invoke(New WaveformAppendDelegate(AddressOf AppendData), WaveformGraph, data(i))
I use the IBM Host Access Class Library for COM Automation as a way to communicate with an IBM AS400 (aka iSeries, IBM i, green screen, 5250) through a terminal emulator. I notice that when you issue a "SendKeys" instruction, control returns to your application before the IBM emulator finishes with the command. This can lead to timing problems because you might then send another "SendKeys" instruction before the system is ready to accept it.
For example:
Imports AutPSTypeLibrary
Imports AutConnListTypeLibrary
Imports AutSessTypeLibrary
Sub Example
Dim connections As New AutConnList
connections.Refresh()
If connections.Count < 1 Then Throw New InvalidOperationException("No AS400 screen can currently be found.")
Dim connection As IAutConnInfo = DirectCast(connections(1), IAutConnInfo)
_Session = New AutSess2
_Session.SetConnectionByHandle(connection.Handle)
Dim _Presentation As AutPS = DirectCast(_Session.autECLPS, AutPS)
_Presentation.SendKeys("PM70[enter]", 22, 8)
_Presentation.SendKeys("ND71221AD[enter]", 22, 20)
End Sub
would work correctly when stepping through code in a debugger, but would fail when running normally because the second instruction was sent too soon.
One way to work with this is to put a timer or loop after each command to slow the calling program down. I consider this less than ideal because the length of time is not always predictable, you will often be waiting longer than necessary to accommodate an occasional hiccup. This slows down the run time of the entire process.
Another way to work around this is to wait until there is a testable condition on the screen as a result of your sent command. This will work sometimes, but some commands do not cause a screen change to test and if you are looking to abstract your command calling into a class or subroutine, you would have to pass in what screen condition to be watching for.
What I would like to find is one of the "Wait" methods that will work in the general case. Options like the autECLScreenDesc class seem like they have to be tailored to very specific conditions.
The autECLPS (aka AutPS) class has a number of Wait methods (Wait, WaitForCursor, WaitWhileCursor, WaitForString, WaitWhileString, WaitForStringInRect, WaitWhileStringInRect, WaitForAttrib, WaitWhileAttrib, WaitForScreen, WaitWhileScreen) but they also seem to be waiting for specific conditions and do not work for the general case. The general case it important to me because I am actually trying to write a general purpose field update subroutine that can be called from many places inside and outside of my .dll.
This example is written in VB.NET, but I would expect the same behavior from C#, C++, VB6, Java; really anything that uses IBM's Personal Communications for Windows, Version 6.0
Host Access Class Library.
The "Operator Information Area" class seems to provide a solution for this problem.
My general case seems to be working correctly with this implementation:
Friend Sub PutTextWithEnter(ByVal field As FieldDefinition, ByVal value As String)
If IsNothing(field) Then Throw New ArgumentNullException("field")
If IsNothing(value) Then Throw New ArgumentNullException("value")
_Presentation.SendKeys(Mid(value.Trim, 1, field.Length).PadRight(field.Length) & "[enter]", field.Row, field.Column)
WaitForEmulator(_Session.Handle)
End Sub
Private Sub WaitForEmulator(ByVal EmulatorHandle As Integer)
Dim Oia As New AutOIATypeLibrary.AutOIA
Oia.SetConnectionByHandle(EmulatorHandle)
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
End Sub
I give thanks to a user named "khieyzer" on this message board for pointing our this clean and general-purpose solution.
Edit:
After a few weeks debugging and working through timing and resource release issues, this method now reads like:
Private Sub WaitForEmulator(ByRef NeededReset As Boolean)
Dim Oia As New AutOIA
Oia.SetConnectionByHandle(_Presentation.Handle)
Dim inhibit As InhibitReason = Oia.InputInhibited
If inhibit = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
End If
If Not Oia.WaitForInputReady(6000) Then
If Oia.InputInhibited = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
Else
Marshal.ReleaseComObject(Oia)
Throw New InvalidOperationException("The system has stopped responding.")
End If
End If
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
Marshal.ReleaseComObject(Oia)
End Sub
I'm building a program in VB whose purpose is to run in the background and automatically update other programs I've created. To do this, it has to check if those programs are still running and, if they are, wait for them to close.
Unfortunately, the program won't compile. The exception states: GetProcessesByName is not a member of String. (The string it refers to is the Process parameter shown in the code below.)
I can't understand why this is happening, because this method has always worked without problems. I'm using Visual Studio 2015. For your reference, here's the code block:
Private Function CheckIfRunning(Process As String) As Boolean
Dim MyProcess() As Process
MyProcess = Process.GetProcessesByName("ProcessName")
If MyProcess.Count > 0 Then
Return True
Else
Return False
End If
End Function
Try using System.Diagnostics.Process.GetProcessesByName("ProcessName")
Since you've declared Process as a string parameter, Process.GetProcessesByName refers to the string instead of the System.Diagnostics method. Alternatively, you can use a different name for the string parameter.
Before I begin, I have to apologize for two things. One is that it is very difficult for me to explain things in a concise manner. Two is that I need to be somewhat vague due to the nature of the company I work for.
I am working on enhancing the functionality of an application that I've inherited. It is a very intensive application that runs a good portion of my company's day to day business. Because of this I am limited to the scope of what I can change--otherwise I'd probably rewrite it from scratch. Anyways, here is what I need to do:
I have several threads that all perform the same task but on different data input streams. Each thread interacts through an API from another software system we pay licensing on to write out to what is called channels. Unfortunately we have only licensed a certain number of concurrently running channels, so this application is supposed to turn them on an off as needed.
Each thread should wait until there is an available channel, lock the channel for itself and perform its processing and then release the channel. Unfortunately, I don't know how to do this, especially across multiple threads. I also don't really know what to search Google or this site for, or I'd probably have my answer. This was my thought:
A class that handles the distribution of channel numbers. Each thread makes a call to a member of this class. When it does this it would enter a queue and block until the channel handling class recognizes that we have a channel, signals the waiting thread that a channel is available and passing it the channel id. I have no idea where to begin even looking this up. Below I have some horribly written PsuedoCode of how in my mind I would think it would work.
Public Class ChannelHandler
Private Shared WaitQueue as New Queue(of Thread)
'// calling thread adds itself to the queue
Public Shared Sub WaitForChannel(byref t as thread)
WaitQueue.enqueue(t)
End Sub
Public Shared Sub ReleaseChannel(chanNum as integer)
'// my own processing to make the chan num available again
End Sub
'// this would be running on a separate thread, polling my database
'// for an available channel, when it finds one, somehow signal
'// the first thread in the queue that its got a channel and here's the id
Public Shared Sub ChannelLoop()
while true
if WaitQueue.length > 0 then
if thereIsAChannelAvailable then '//i can figure this out my own
dim t as thread = ctype(WaitQueue.dequeue(), Thread)
lockTheChannel(TheAvailableChannelNumber) 'performed by me
'// signal the thread, passing it the channel number
t => SignalReady(theAvailableChannelNumber) '// how to signal?
end if
end if
end while
End Sub
End Class
and then
'// this inside the function that is doing the processing:
ChannelHandler.requestChannel(CurrentThread)
while (waitingForSignal) '// how?
block '// how?
dim channelNumber as int => getChannelNumberThatWasSignaledBack
'// perform processing with channelNumber
ChannelHandler.ReleaseChannel(channelNumber)
I am working with the .NET Framework 3.5 in VB.NET. I am sure there has got to be some sort of mechanism already built for this, but as I said I have no idea exactly what keywords I should be searching for. Any input pointing me in the right direction (ie specific .NET framework classes to use or code samples) would be greatly appreciated. If I need to elaborate on anything, please let me know and I will to the best of my ability.
Edit: The other problem that I have is that these channels can be turned on/off from outside of this application, manually by the user (or as a result of a user initiated event). I am not concerned with a channel be shut down while a thread is using it (it would throw an exception and then pick back up next time it came through. But the issue is that there are not a constant number of threads fighting over a constant number of channels (if a user turns one on manually, the count is reduced, etc). Both items are variable, so I cant rely on the fact that there are no external forces (ie, something outside this set of threads, which is why I do some processing via my DB to determine an available channel number)
What I would do:
Switch the System.Threading.Thread by the System.Threading.Tasks.Task class.
If a new Task needs to be created, but the List(Of Task) (or, in your example, Queue(Of Task) ) count greater than the maximum permitted, use the Task.WaitAny method.
EDIT:
As I answered the previous block on my phone (which is pretty challenging for writing code), let now me write an example about how I would do it:
Imports System.Threading.Tasks
Imports System.Collections.Generic
Public Class Sample
Private Const MAXIMUM_PERMITTED As Integer = 3
Private _waitQueue As New Queue(Of Task)
Public Sub AssignChannel()
Static Dim queueManagerCreated As Boolean
If Not queueManagerCreated Then
Task.Factory.StartNew(Sub() ManageQueue())
queueManagerCreated = True
End If
Dim newTask As New Task(Sub()
' Connect to 3rd Party software
End Sub)
SyncLock (_waitQueue)
_waitQueue.Enqueue(newTask)
End SyncLock
End Sub
Private Sub ManageQueue()
Dim tasksRunning As New List(Of Task)
While True
If _waitQueue.Count <= 0 Then
Threading.Thread.Sleep(10)
Continue While
End If
If tasksRunning.Count > MAXIMUM_PERMITTED Then
Dim endedTaskPos As Integer = Task.WaitAny(tasksRunning.ToArray)
If endedTaskPos > -1 AndAlso
endedTaskPos <= tasksRunning.Count Then
tasksRunning.RemoveAt(endedTaskPos)
Else
Continue While
End If
End If
Dim taskToStart As Task
SyncLock (_waitQueue)
taskToStart = _waitQueue.Dequeue()
End SyncLock
tasksRunning.Add(taskToStart)
taskToStart.Start()
End While
End Sub
End Class