Weird behavior using the Observer pattern - vb.net

Ok, so I have an application that reads another processes memory. I initially had multiple scanning threads for the various areas I needed to read. This was processor intensive so I decided to go with the observer pattern. All was well except that I am having a weird behavior.
Here is what is happening
I have 2 radars (overlay and mapped) Both have a watcher class that attaches to the memory scanner and is notified on a new list of mobs.
so I open radar 1 (mapped) it attaches it's watcher to the scanner and waits for mob list update notifications
Open radar 2 (overlay). same thing happens and another watcher is attached.
all is well and good so far
Now there are properies on the mobs in the list, one of which is IsFilteredOut. This property is set in the radar code after it receives the list.
Now the weird behavior is that no matter what I do, the second radar to be opened changes all the properties of the mobs in the list of both radars. It is as if I am passing the list by ref, but I am not. I actually create a new instance of the moblist class every time I pass the list.
Here is the notify code. As you can see I create a new instance of the moblist class each pass.
Private Sub NotifyMobListUpdated(ByVal Mobs As List(Of MobData))
If Mobs IsNot Nothing Then
For Each w As Watcher In _watchers
If w.Type And WatcherTypes.MobList = WatcherTypes.MobList OrElse w.Type And WatcherTypes.All = WatcherTypes.All Then
w.MobListUpdated(New MobList(Mobs))
End If
Next
End If
End Sub
This is where it is handled in the Watcher class
''' <summary>
''' IWatcher MoblistUpdated Implementation
''' </summary>
''' <param name="Mobs">The Updated mob list</param>
''' <remarks></remarks>
Public Sub MobListUpdated(ByVal Mobs As MobList) Implements IWatcher.MobListUpdated
Try
PostNewMobList(Mobs)
Catch ex As Exception
End Try
End Sub
Public Sub PostNewMobList(ByVal Mobs As MobList)
_sync.Post(New SendOrPostCallback(AddressOf OnNewMobList), Mobs)
End Sub
Private Sub OnNewMobList(ByVal state As Object)
Dim mobs As MobList = TryCast(state, MobList)
Try
If mobs IsNot Nothing Then
RaiseEvent NewMobList(mobs)
End If
Catch ex As Exception
End Try
End Sub
This error is driving me nuts and any help would be greatly appreciated.
Thanks

I actually create a new instance of the moblist class every time I pass the list.
Which only prevents the list from changing, not the list elements. You'd have to clone the element objects as well. I don't have a clue with radars and mobs do, you might want to consider using Send instead of Post.

Related

UWP adding UI control to list in non-uit thread

So as i'm learning more and more stuff of UWP and XAML i bumped into two issues, one is (i think) "navigation" related and the second a threading issue. What i'm trying to achieve is simple. I have two pages, one "home" and one "Settings". On the Home page i show the connected clients as Custom_Buttons. On the Settings page i can change some settings regarding the app and Connected Clients
Navigation Issue
On my MainPage is setup all my declarations and object classes i need. When i navigate to a page i pass me (that is the MainPage) through to the page i'm loading so i can use the properties and objects in the that i declared on the MainPage. Then when i load the page i use the page event OnNavigatedTo to handle the passed MainPage and do local stuf with it. When i switch often between the pages the app crashes and opens the page app.g.i.vb and point to the following code:
#If Debug AndAlso Not DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION Then
AddHandler Me.UnhandledException,
Sub(sender As Global.System.Object, unhandledExceptionArgs As Global.Windows.UI.Xaml.UnhandledExceptionEventArgs)
If Global.System.Diagnostics.Debugger.IsAttached Then
**Here--->>>** Global.System.Diagnostics.Debugger.Break()
End If
End Sub
#End If
And the navigation code:
Private Sub ListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
If Home.IsSelected AndAlso Not ScenarioFrame.CurrentSourcePageType Is GetType(Home) Then
BackButton.Visibility = Visibility.Collapsed
ScenarioFrame.Navigate(GetType(Home), Me)
ElseIf Settings.IsSelected AndAlso Not ScenarioFrame.CurrentSourcePageType Is GetType(Settings) Then
BackButton.Visibility = Visibility.Visible
ScenarioFrame.Navigate(GetType(Settings), Me)
End If
End Sub
Threading Issue
On the MainPage I declare a class i wrote called TCP_Server. This class has a StreamSocketListener that uses the event ConnectionReceived to accept new incoming clients. I then simply create a new Object that represents a UI form of the client and pass it the StreamSocket that comes in the Event Args in the sub new. In this way each object can handles it's own Read and Write directly from the StreamSocket Then i add this new object to a ObservableCollection(Of Object) which is held in the TCP_Server Class. This list is bound to the ItemsSource of a Canvas that i use on the HomePage which is not my MainPage.
Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
MyBase.OnNavigatedTo(e)
If ButtonsList.ItemsSource = Nothing Then ButtonsList.ItemsSource = DirectCast(e.Parameter, MainPage).TCP_Server.Clients
End Sub
When i create this new object in the ConnectionReceived i get an error System.Exception: 'The application has called an interface that has been marshalled for another thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD)) '. It only works when i use the Dispatcher.RunAsync
Private Async Sub TCP_Listener_ConnectionReceived(sender As StreamSocketListener, args As StreamSocketListenerConnectionReceivedEventArgs) Handles TCP_Listener.ConnectionReceived
'Check if the client already excists or not.
Dim client As Client_Button = Clients.FirstOrDefault(Function(x) x.IPaddr = args.Socket.Information.RemoteAddress.ToString)
rootPage.NotifyUser("New Client connected! : [" & args.Socket.Information.RemoteAddress.ToString & "] Total Connected clients = " & Clients.Count, NotifyType.Message)
If client Is Nothing Then
Await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, Function()
'Create New object
Dim x As New Client_Button(args.Socket)
'Create new task that runs Async to process incomming data
Dim tsk As Task = Task.Run(Sub() x.ProcessClientAsync())
'Add to the task list so we can stop it later on
ClientTasks.Add(tsk)
'Add it to the Clients List so we can work with the objects
Clients.Add(x)
Return True
End Function)
Else
Await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, Function()
client = Nothing
Clients.Remove(client)
'Create New object
Dim x As New Client_Button(args.Socket)
'Create new task that runs Async to process incomming data
Dim tsk As Task = Task.Run(Sub() x.ProcessClientAsync())
'Add to the task list so we can stop it later on
ClientTasks.Add(tsk)
'Add it to the Clients List so we can work with the objects
Clients.Add(x)
Return True
End Function)
End If
End Sub
For "Navigation Issue" you described here, navigation between pages several times it will crash, please try to set NavigationCacheMode of the page to Required or Enabled as follows:
Public Sub New()
Me.InitializeComponent()
Me.NavigationCacheMode = NavigationCacheMode.Required
End Sub
Details please reference remarks of Page class. If you still have issues please provide the details about the "UnhandledException" .
For "Threading Issue", using Core​Dispatcher is the correct way and this is by design. ConnectionReceived triggered in a non-UI thread, but you invoked UI thread inside this event handle, so you need Dispatcher.RunAsync. More details you can reference this similar thread.

Ensure maintainability & testability in file-processing application

Problem
I want to refactor an old application which has grown over time and is awful to maintain/test. Basically the app needs to read an file an process it lines.
The file to be read can be compared to a CSV-file, where the first field determines the type of the line, something like this:
1,abcd,...
1,3423,...
2,"abc",...
5,test,...
Currently the application works like this (pseudo-code):
For Each line in file.getLines()
if (line.StartsWith("1,") then
' Process line of type 1
elseif (line.StartsWith("2,") then
' Process line of type 2
...
End If
Next
This obviously is a nightmare to maintain and test. Therefore I'd like refactor this whole thing and thought of doing it like this:
Create a parser for each line-type
Let each parser register itself to a handler, which will raise an event for each line
Code Idea
Handler:
Public Class ParserHandler
Public Event Parse(RepLine As ReportLine)
Public Event FileFinished()
Public Sub RaiseParse(RepLine As ReportLine)
RaiseEvent Parse(RepLine)
End Sub
Public Sub RaiseFileFinished()
RaiseEvent FileFinished()
End Sub
End Class
Parser:
Public Class FirstParser
Public Sub New(Handler As ParserHandler)
AddHandler Handler.Parse, AddressOf Parse
AddHandler Handler.FileFinished, AddressOf FileFinished
End Sub
Public Sub Parse(RepLine As ReportLine)
If Not RepLine.Type = 1 Then
Return
End If
' Process
End Sub
Private Sub FileFinished()
' Final stuff, eg. insert into DB
End Sub
End Class
Main:
Dim ParserHandler As New ParserHandler()
Dim FirstParser As New FirstParser(ParserHandler)
Dim SecondParser As New SecondParser(ParserHandler)
...
For Each line in file.getLines()
' Extract Id and construct ReportLine-object here
ParserHandler.RaiseParse(ReportLine)
Next
I think this looks way better than the original version, but I'm still not convinced that this is really the way to go. In case of testing the parsers with unit tests, I still have to create a handler first. This might be ok, but feels wrong as the parser should be testable separately (is it wrong?).
I like the use of events in this case, as not every parser needs to implement every possible event of the handler, which prevents me from just having an IParser interface.
What do you think of this approach, is it good to go? Are there any best practices/design pattern I should look into for a clean solution to this problem?

Updating Variable in Multithreading in VB.NET

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

How to update main form from thread utilizing a module creates new mainform?

So my use for a module is so I can use the same functions across different programs that I develope for my employer. They also want my module to be distributed amongst other programmers so they can use it as well. The programs need to know when there is a thread still running (SQL code is running (there are no problems with the sql side) and it needs to notify the user when all work is done but the user needs to be able to queue work)
From the main form I am using this code:
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start()
This is the code for my module:
Public Sub Testing()
Form1.threadsrunning += 1
Form1.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
Form1.threadsrunning -= 1
Form1.accesscontrolsmoduletesting()
end sub
The code to access the controls on the main form is
Public Sub accesscontrolsmoduletesting()
If Me.InvokeRequired = True Then
Me.Invoke(New MethodInvoker(AddressOf accesscontrolsmoduletesting))
Else
If threadsrunning > 0 Then
Label4.Text = threadsrunning & " threads running"
Else
Label4.Text = "0 threads running"
End If
End If
End Sub
I already know the issue is the new thread is creating a new form. I tested this by showing the form and making it wait so it didnt immediately dispose itself and I seen the label was updated. How do I make this thread update the main form instead of just creating a new mainform and then disposing itself after the thread dies?
To reiterate on my Comment you need to get the actual Form1 that is being shown, you should change your Testing Method to accept a Parameter of Form1, then you can use a Parameterized Thread.Start to pass in the Calling Form. You are running into a feature that was left in place to placate Vb6 programmers transitioning to VB.net as this answer by Hans states. And you may find this Blog Post by John Mcllhinney an interesting read.
From Second Link(emphasize mine):
In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.
So in response to above I would change your Module Test Method to:
Public Sub Testing(myForm As Form1)
myForm.threadsrunning += 1
myForm.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
myForm.threadsrunning -= 1
myForm.accesscontrolsmoduletesting()
End Sub
And I would change your Form1's Thread Start Code to look like this.
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start(Me) 'Note the passing in the instance of the calling Form
After making these few changes your code will work

Coded UI test takes 2-3 minutes to pass

So I am working with Coded UI to run some automated tests and one of the tests takes an extremely long time to complete.
I used breakpoints and found that after End Get in the bottom block of code, nothing happens for minutes and then finally the Assert.AreEqual completes and the test passes.
I tried changing a few playback settings but nothing changed. Does anyone know what is going on, and how I can fix it?
-The first block of code is a generated Assert method that gets called by the test
-The second block of code is called in the Assert parameters
'''<summary>
'''assert_clicked_columnhead_requestor - Use 'assert_clicked_columnhead_requestorExpectedValues' to pass parameters into this method.
'''</summary>
Public Sub assert_clicked_columnhead_requestor()
Dim uINameCell As HtmlCell = Me.UIAppWindow1.UIAppDocument.UIX978532666mkrdataTblTable1.UINameCell
'Verify that 'Name' cell's property 'InnerText' equals 'Name'
Assert.AreEqual(Me.assert_clicked_columnhead_requestorExpectedValues.UINameCellInnerText, uINameCell.InnerText)
End Sub
Public Overridable ReadOnly Property assert_clicked_columnhead_requestorExpectedValues() As assert_clicked_columnhead_requestorExpectedValues
Get
If (Me.massert_clicked_columnhead_requestorExpectedValues Is Nothing) Then
Me.massert_clicked_columnhead_requestorExpectedValues = New assert_clicked_columnhead_requestorExpectedValues()
End If
Return Me.massert_clicked_columnhead_requestorExpectedValues 'PROBLEM HERE
End Get
End Property
As requested
'''<summary>
'''click_columnhead_requestor
'''</summary>
Public Sub click_columnhead_requestor()
Dim uIRequestorCell As HtmlHeaderCell = Me.UIAppWindow1.UIAppDocument.UIRequestorCell
'Click 'Requestor' cell
Mouse.Click(uIRequestorCell, New Point(51, 23))
End Sub
Public ReadOnly Property UIAppWindow1() As UIAppWindow1
Get
If (Me.mUIAppWindow1 Is Nothing) Then
Me.mUIAppWindow1 = New UIAppWindow1()
End If
Return Me.mUIAppWindow1
End Get
End Property
Public ReadOnly Property UIX978532666mkrdataTblTable1() As UIX978532666mkrdataTblTable1
Get
If (Me.mUIX978532666mkrdataTblTable1 Is Nothing) Then
Me.mUIX978532666mkrdataTblTable1 = New UIX978532666mkrdataTblTable1(Me)
End If
Return Me.mUIX978532666mkrdataTblTable1
End Get
End Property
Coded UI does searches for precisely what is given in the search and filter properties, that operates quite fast. However, if that search fails then Coded UI does a smart match trying to find something similar and that can take a long time. It is necessary because titles etc can change a little from run to run. Changing the tests to avoid needing a smart match can dramatically improve the search speed. The general approach is to modify the search from equality to contains and to remove the part of the string that changes. A Microsoft blog explains in more detail, see http://blogs.msdn.com/b/dave_froslie/archive/2012/08/10/why-do-my-coded-ui-tests-pause-during-playback.aspx
¿? have you tried changing the value of ..
Playback.PlaybackSettings.SearchTimeout = miliseconds;
msdn playbacksettings.searchtimeout
good luck and tell us something