VB.Net Async - Check large list of string for a match - vb.net

I need this function to run Async, but can't seem to figure a way to do it.
LIST1 is Public and contains a List(of String) with a few hundred entries. List Declaration:
Public LIST1 As New List(Of String)
Normally, I'd run the following code to retrieve the boolean of whether or not he list contains the entry:
Dim result = LIST1.Any(Function(s) value.ToLower.Contains(s))
Full non-Async function:
Function CheckValue(byval value As String) As Boolean
Dim result As Boolean = LIST1.Any(Function(s) value.ToLower.Contains(s))
Return result
End Function
That works well as expected.
How would I implement the same as an Async function? I've tried:
Async Function CheckValue(byval value as String) As Task(Of Boolean)
Dim result as Task(Of Boolean) = Await LIST1.Any(Function(s) value.ToLower.Contains(s))
Return result
End Function
I get the following error: 'Await' requires that the type 'Boolean' have a suitable GetAwaiter method.
Any thoughts?

It does not return a task, so there is no reason to await it. If your concern is that it is too slow, you can run any synchronous code in a new thread, and then await the completion of that thread, like this:
Dim result As Boolean = Await Task.Run(Function() LIST1.Any(Function(s) value.ToLower.Contains(s)))
Or, as GSerg mentioned, though it technically doesn't make it awaitable, you can use AsParallel.Any:
Dim result As Boolean = LIST1.AsParallel.Any(Function(s) value.ToLower.Contains(s))
However, be aware that starting new threads has a fair amount of overhead, so starting up a new thread, just to do a small amount of work, may make it actually run slower.
In this particular case, if performance is key, I would recommend looking into various search/indexing algorithms. For instance, take a look at some of the ones mentioned here. I wouldn't be surprised if there are opensource .NET libraries for those kinds of algorithms.

Related

Unexpected results when using `Task.Run` to call a synchronous method

I'm working on an application where at some point i'm doing some CPU intensive calculation on the kinematics of an object. At some point i'm using Task.Run() to call the function doing the work, this leads to some unexpected results, i'm not sure if this would be considered a race condition or if it has some other name. My real code is rather expansive, so i have reduced the issue to what i believe to be a minimal working example, to be run in a .net framework console application.
For the MWE consider the following class, it has 3 fields and a constructor initializing them. It also has a report() sub for easier debugging.
Public Class DebugClass
Public Variable1 As Double
Public Variable2 As Double
Public Variable3 As Double
Public Sub New(Variable1 As Double, Variable2 As Double, Variable3 As Double)
Me.Variable1 = Variable1
Me.Variable2 = Variable2
Me.Variable3 = Variable3
End Sub
Public Sub Report()
Console.WriteLine()
Console.WriteLine("Variable1: {0},Variable2: {1},Variable3: {2}", Variable1, Variable2, Variable3)
End Sub
End Class
I also have another helper function which replaces the CPU intensive work that my real application would have, with a random delay between 0 and 1 second :
Public Async Function RandomDelayAsync() As Task
Await Task.Delay(TimeSpan.FromSeconds(Rnd()))
End Function
For demonstration purposes I have 2 version of my "work" function; an Async an non-Async version. Each of these functions takes and instance of DebugClass as a parameter, pretends to do some work on it and then simply returns the same object that it got as an input. :
'Async version
Public Async Function DoSomeWorkAsync(WorkObject As DebugClass) As Task(Of DebugClass)
Await RandomDelayAsync()
Return WorkObject
End Function
'Synchronous version
Public Function DoSomeWork(WorkObject As DebugClass) As DebugClass
RandomDelayAsync.Wait()
Return WorkObject
End Function
Lastly, i have a WaiterLoop, this function Awaits the created tasks for one to complete, prints the returned object's fields to the console and removes it from the list of tasks. It then waits for the next one to complete. In my real application i would do some more calculations here after i get the results from the individuals tasks, to see which parameters give the best results.
Public Async Function WaiterLoop(TaskList As List(Of Task(Of DebugClass))) As Task
Dim Completed As Task(Of DebugClass)
Do Until TaskList.Count = 0
Completed = Await Task.WhenAny(TaskList)
Completed.Result.Report()
TaskList.Remove(Completed)
Loop
End Function
Now, first consider this version of my Main() function:
Sub Main()
Randomize()
Dim Tasklist As New List(Of Task(Of DebugClass))
Dim anInstance As DebugClass
For var1 As Double = 0 To 5 Step 0.5
For var2 As Double = 1 To 10 Step 1
For Var3 As Double = -5 To 0 Step 1
anInstance = New DebugClass(var1, var2, Var3)
'adding an Async task to the tasklist
Tasklist.Add(DoSomeWorkAsync(anInstance))
Next
Next
Next
WaiterLoop(Tasklist).Wait()
Console.ReadLine()
End Sub
The output here is exactly as I would expect, the tasks all complete and for each of the parameter combinations made a line is printed to the console. All's good so far, the problem i'm facing arrises when this line:
Tasklist.Add(DoSomeWorkAsync(anInstance))
Is replaced with this line
Tasklist.Add(Task.Run(Function() DoSomeWork(anInstance)))
In this new version i don't call the Async version of the work function, instead i'm using Task.Run To run a normally synchronous function on a worker thread. This is where the s**t hits the fan.
Suddenly, the output is not as expected anymore;
'This is the type of output i now get:
Variable1: 1.5,Variable2: 7,Variable3: -1
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Variable1: 5,Variable2: 10,Variable3: 0
Somehow all of the tasks i created now seem to be referring to the same instance of DebugClass, as everytime a tasks completes, the same output is printed. I don't understand why this happens, because i'm creating a new instance of DebugClass before each time i start a new task: anInstance = New DebugClass(var1, var2, Var3) followed by Tasklist.Add(Task.Run(Function() DoSomeWork(anInstance))). As soon as i assign a new instance of DebugClass to AnInstance, it "forget's" the previous instance it was storing, right?. And the instance referenced by each of the created tasks ought to be independent of the ones referred to by the other tasks?
Clearly, I am mistaken, but i would appreciate it if someone could explain to me what's going on here.
I also wonder why one is faster than the other, but i will save that for a separate question unless it's related to this issue.
Thank you for taking the time to read this.
Lambdas (ie Function() DoSomeWork(anInstance)) 'close'* on a reference to a variable NOT on its current value.
Thus Function() DoSomeWork(anInstance) means 'when you come to run perform the DoSomeWork method on the current value of anInstance'.
You only have one instance of anInstance because you declared it outside the loop.
Quick fix: Move the Dim anInstance As DebugClass statement inside the inner loop, this gives you one variable instance per loop, which is what you want.
See also Captured variable in a loop in C#, which is this basically the same question in c# and has some useful discussion/links in the comments
*Closures are a big topic, I'd suggest reading https://en.wikipedia.org/wiki/Closure_(computer_programming). Happy to discuss further in comments.

Can't get a list of declared methods in a .net class

Having read a great many posts on using reflection to get a list of methods in a given class, I am still having trouble getting that list and need to ask for help. This is my current code:
Function GetClassMethods(ByVal theType As Type) As List(Of String)
Dim methodNames As New List(Of String)
For Each method In theType.GetMethods()
methodNames.Add(method.Name)
Next
Return methodNames
End Function
I call this method like this:
GetClassMethods(GetType(HomeController))
The return has 43 methods, but I only want the methods I wrote in the class. The image below shows the beginning of what was returned. My declared methods are in this list, but down at location 31-37. There are actually 9 declared methods, but this list doesn’t show the Private methods.
When I look at theType, I see the property I want. It is DeclaredMethods which shows every declared method, public and private.
However, I’m not able to access this property with a statement such as this.
Dim methodList = theType.DeclaredMethods
The returned error is that DelaredMethods is not a member of Type. So, my questions are multiple:
1) Most important, what code do I need to retrieve every declared method in the class, and only the methods I declared?
2) Why am I not able to access the property that gives the list of DeclaredMethods()?
Try this:
Function GetClassMethods(ByVal theType As Type) As List(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Dim result = theType.GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
Return result.ToList()
End Function
or for some fun with generics:
Function GetClassMethods(Of T)() As List(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Dim result = GetType(T).GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
Return result.ToList()
End Function
The IsSpecialName filter excludes methods with compiler-generated names, such as the special methods used by the compiler to implement properties. You can also play around more with the flags if you need to include, say, NonPublic members as well.
Finally, whenever you have a method ending with Return something.ToList() (or which could end with it, as my adaption shows here), it's almost always better to change the method to return an IEnumerable(Of T) instead, and let the calling code call ToList() if it really needs it. So my first example above is really better like this:
Function GetClassMethods(ByVal theType As Type) As IEnumerable(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Return theType.GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
End Function
Hey, that could be a single-liner. Then for those situations where you really need a list, you can do this:
Dim methodNames As List(Of String) = GetClassMethods(HomeController).ToList()
You'll start to find in many situations you don't need to use ToList() at all; the generic IEnumerable was good enough. Certainly this is true anywhere you just use the result with For Each loop. Now suddenly the memory use in your programs are significantly reduced.

Async and LINQ Queries: Retrieve all lines in all files in a directory tree

I have some UWP code that I am trying to maintain. Its goal is to coalesce some plaintext files kept in a directory structure into a single XML file.
In the interest of doing this, I previously grab all distinct lines in all files in the dirrectory structure using LINQ:
Imports System.IO
Async Function getAllLines() As IEnumerable(Of String)
Dim _folderPicker As New Windows.Storage.Pickers.FolderPicker()
_folderPicker.FileTypeFilter.Add("*") 'We must add this; it is a known bug
Dim _rootDir As Windows.Storage.StorageFolder = Await _folderPicker.PickSingleFolderAsync()
Dim allDistinctLines As IEnumerable(Of String) = From _file In Directory.GetFiles(_rootDir.Name, "*.txt", SearchOption.AllDirectories) From line In File.ReadAllLines(_file) Where line.Trim().Length > 0 Select line.Trim() Distinct
Return allDistinctLines
End Function
Since late October, however, it seems some things have changed regarding file perrmissions (Source). I came back to the code after about a month of it working to find it throwing an exception saying that the folder that I was previously able to write to was not accessable due to permissions. The folder existed and I grabbed the folder from a picker so I thought that I was good. Previously, as long as I had grabbed a File or Folder using a picker, I could still use a System.IO function to enumerate, read from or write to said folder or directory.
Now, it seems as though you must go though the proper UWP faculties, which is fine, but I am unsure how to rewrite my LINQ query as the UWP methods are asynchronous.
Here is what I have tried:
Async Function getAllLines() As IEnumerable(Of String)
Dim _folderPicker As New Windows.Storage.Pickers.FolderPicker()
_folderPicker.FileTypeFilter.Add("*") 'We must add this; it is a known bug
Dim _rootDir As Windows.Storage.StorageFolder = Await _folderPicker.PickSingleFolderAsync()
Dim queryOptions As New Windows.Storage.Search.QueryOptions()
queryOptions.FileTypeFilter.Add(".txt")
queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep
Dim allDistinctLines As IEnumerable(Of String) = From _file In Await _rootDir.CreateFileQueryWithOptions(queryOptions).GetFilesAsync() From line In Await Windows.Storage.FileIO.ReadLinesAsync(_file) Where line.Trim().Length > 0 Select line.Trim() Distinct
Return allDistinctLines
End Function
In the above code, I am at least able to enumerate the folder if I comment out the second await in the LINQ. The issue is that with the second Await in the LINQ query, I cannot compile as
'Await' may only be used in a query expression within the first collection expression of the initial 'From' clause or within the collection expression of a 'Join' clause.
So I read up on Join clauses, which seem to equate disparate data, which isn't really what I am looking for.
I read up on the error in general and it seems like the LINQ queries and Async Functions only have limited functionality. Honestly, I don't know enough about LINQ to really make that claim, but that was my feeling.
I found this answer, which uses Task.WhenAll() to facilitate the LINQ and Async Functions usage, but I can't wrap my head around exactly how it would be put into practice.
How to await a method in a Linq query
So I tried:
Async Function getAllLines() As IEnumerable(Of String)
Dim _folderPicker As New Windows.Storage.Pickers.FolderPicker()
_folderPicker.FileTypeFilter.Add("*") 'We must add this; it is a known bug
Dim _rootDir As Windows.Storage.StorageFolder = Await _folderPicker.PickSingleFolderAsync()
Dim queryOptions As New Windows.Storage.Search.QueryOptions()
queryOptions.FileTypeFilter.Add(".txt")
queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep
Dim allFiles As IEnumerable(Of Windows.Storage.StorageFile) = From _file In Await _rootDir.CreateFileQueryWithOptions(queryOptions).GetFilesAsync() Select _file
Dim allDistinctLines As IEnumerable(Of String) = Task.WhenAll(allFiles.Select(Function(_file) Windows.Storage.FileIO.ReadLines.Async(_file).Where(Function(_line) _line.Trim().Length > 0).Distinct()))
Return allDistinctLines
End Function
But the return type isn't the same, it ends up being some strange IList(Of String)(), which doesn't work for me strangely. Maybe there is just a critical misunderstanding there.
Regardless, any help understanding Async file operations is appriciated!
As Stephen said in this thread, LINQ has very limited support for async/await. I recommend you use For Each in conjunction with Windows.Storage api instead like the follow:
Async Function getAllLines() As Task(Of IEnumerable(Of String))
Dim _folderPicker As New Windows.Storage.Pickers.FolderPicker()
_folderPicker.FileTypeFilter.Add("*") 'We must add this; it is a known bug
Dim _rootDir As Windows.Storage.StorageFolder = Await _folderPicker.PickSingleFolderAsync()
Dim queryOptions As New Windows.Storage.Search.QueryOptions()
queryOptions.FileTypeFilter.Add(".txt")
queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep
Dim allDistinctLines As New List(Of String)
For Each file As StorageFile In Await _rootDir.CreateFileQueryWithOptions(queryOptions).GetFilesAsync()
For Each line In Await Windows.Storage.FileIO.ReadLinesAsync(file)
If line.Trim().Length > 0 Then
allDistinctLines.Add(line.Trim())
End If
Next
Next
Return allDistinctLines.Distinct()
End Function

Start task without waiting

I would like to start a background task without using the Await keyword. Instead I want to monitor the task at various points and restart it when necessary to update information in the background. Here is the method I am trying to call:
Public Async Function UpdateVehicleSummaries(p_vehicleID As Int32) As Task(Of Boolean)
Dim tempVehicle As Koolsoft.MARS.BusinessObjects.Vehicle
For Each tempVehicle In Vehicles
If p_vehicleID = 0 Or p_vehicleID = tempVehicle.VehicleID Then
Await UpdateVehicleStats(tempVehicle)
End If
Next
Return True
End Function
The code I am trying to start the task doesn't seem to work and I'm not sure how to provide the parameter. I get an error that "Task(Of Boolean) cannot be converted to System.Action and or an error on the parameter"
Dim tempTask As Task
tempTask = New Task(UpdateVehicleSummaries(tempVehicleID))
tempTask.Start()
Any help would be appreciated.
Since UpdateVehicleSummaries is already asynchronous, you should be abel to just do:
Dim tempTask As Task(Of Boolean) = UpdateVehicleSummaries(tempVehicleID)
The returned Task(Of T) will be "hot" (running), but shouldn't block, as the Await call will immediately return control flow to the caller at that point.
A more typical use of this method, if you need to perform other work while this runs, would be to do the following:
Dim tempTask = UpdateVehicleSummaries(tempVehicleID)
' Do your other work
Dim success = Await tempTask ' Get the boolean result asynchronously...
' use the result

Collection was modified; enumeration operation may not execute

I cannot get to the bottom of this error because it happens only in one instance, and I can't find any code that can be the cause of the error.
I have a 3.5 web service that I'm calling from a multi-threaded, CAB client. I have a bunch of unit tests against the web service (from both 3.5 and 2.0 code), and it works fine. However, in the actual application, it doesn't work 90% of the time and, the remaining 10% of the time, it decides to work.
The code:
Friend Function ExecuteSearch(ByVal query As String) As List(Of SomeObject)
Dim searchResults As List(of Object) = _searcher.UserSearch(query)
Return searchResults
End Function
// In Searcher
Public Function UserSearch(ByVal query As String) As List(Of SomeObject)
Return Translate(Search.GetResults(query))
End Function
// In Search
Public Function GetResults(ByVal query As String) As List(Of SomeObject)
Dim service As New FinderService.FinderService()
Dim results As New List(Of String)
Dim serviceResults As IEnumerable(Of String) = service.Search(query) // <-- ERRORS OUT HERE
results.AddRange(serviceResults)
Return results
End Function
// In the service
Public Function Search(ByVal query As String) As IEnumerable(Of String)
Initialize() // Initializes the _accounts variable
Dim results As New List(of String)
For Each account As User In _accounts
If a bunch of conditions Then
results.Add(account.Name)
End IF
End For
Return results
End Function
The breakpoints hit these codes (in this order). The line that errors out is in the "GetResults" method.
Any help would be appreciated.
Ah, the Heisenbugs :D
Apparently _accounts get modified during the loop. You can alleviate it by doing
For Each account As User In _accounts.ToList()
so a copy of current _accounts is created and enumerated and not the actual collection that might change