Async and LINQ Queries: Retrieve all lines in all files in a directory tree - vb.net

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

Related

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

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.

Returning list in async function in uwa vb.net

I am trying to parse through some xml files and return the results to a datagrid in UWA. The code builds fine but when running it returns
Unable to cast object of type 'System.Threading.Tasks.Task1[System.Collections.Generic.List1[Festplatten_Archiv_Client.Drive]]' to type 'System.Collections.IEnumerable'.
In my XAML.vb I am only calling the class to create the files and set the results as fileSource:
Public Sub New()
InitializeComponent()
dataGrid.ItemsSource = Drive.Drives
End Sub
Which works fine if I only add a sample Drive with
drivelist.Add(New Drive("Name",0, 0, 0), "location", "date"))
But as I want to parse through the XMLs, this is my code.
This is my drives class:
Public Shared Async Function Drives() As Task(Of List(Of Drive))
Dim drivelist As New List(Of Drive)
Dim folderpicked As StorageFolder
Try
folderpicked = Await StorageApplicationPermissions.FutureAccessList.GetItemAsync(ReadSetting("folderStorageToken"))
Catch ex As Exception
Debug.WriteLine("Fehler: " & ex.Message)
folderpicked = Nothing
End Try
Dim xmlfiles As List(Of StorageFile) = Await folderpicked.GetFilesAsync()
For Each file In xmlfiles
''Process files
Next
Return Await Task.Run(Function() drivelist)
End Function
It might be something with async programming, but I am very new to this. Thanks for any help!
You can make a blocking call to an Async routine from the ctor as follows:
Dim drivesResult = Drives().GetAwaiter().GetResult()
This is effectively forcing the routine to execute synchronously. If that's not what you want, then you'll need to explore a different alternative, e.g. the suggestion in the comments.

Calling System.IO.ReadAllBytes by string name

This post is related to Visual Basic .NET 2010
So, I'm wondering if there's any way to call a function from a library such as System.ReadAllBytes by string name.
I've been trying Assembly.GetExecutingAssembly().CreateInstance and System.Activator.CreateInstance followed by CallByName(), but none of them seemed to work.
Example of how I tried it:
Dim Inst As Object = Activator.CreateInstance("System.IO", False, New Object() {})
Dim Obj As Byte() = DirectCast(CallByName(Inst, "ReadAllBytes", CallType.Method, new object() {"C:\file.exe"}), Byte())
Help is (as always) much appreciated
It is System.IO.File.ReadAllBytes(), you missed the "File" part. Which is a Shared method, the CallByName statement is not flexible enough to permit calling such methods. You will need to use the more universal Reflection that's available in .NET. Which looks like this for your specific example, spelled out for clarity:
Imports System.Reflection
Module Module1
Sub Main()
Dim type = GetType(System.IO.File)
Dim method = type.GetMethod("ReadAllBytes")
Dim result = method.Invoke(Nothing, New Object() {"c:\temp\test.bin"})
Dim bytes = DirectCast(result, Byte())
End Sub
End Module

How to write a simple Expression-like class in .NET 2.0?

I'm currently working in .NET 2.0 Visual Basic. The current project is an Active Directory Wrapper class library within which I have a Searcher(Of T) generic class that I wish to use to search the underlying directory for objects.
In this Searcher(Of T) class I have the following methods:
Private Function GetResults() As CustomSet(Of T)
Public Function ToList() As CustomSet(Of T)
Public Function Find(ByVal ParamArray filter() As Object) As CustomSet(Of T)
// And some other functions here...
The one that interests me the most is the Find() method to which I can pass property and values and would like to parse my LDAP query from this filter() ParamArray parameter. Actually, all I can figure out is this:
Public Sub SomeSub()
Dim groupSearcher As Searcher(Of Group) = New Searcher(Of Group)()
Dim groupsSet as CustomSet(Of Group) = groupSearcher.Find("Name=someName", "Description=someDescription")
// Working with the result here...
End Sub
But what I want to be able to offer to my users is this:
Public Sub SomeSub()
Dim groupSearcher As Searcher(Of Group) = New Searcher(Of Group)()
Dim groupsSet As CustomSet(Of Groupe) = groupSearcher.Find(Name = "someName", Guid = someGuid, Description = "someDescription")
// And work with the result here...
End Sub
In short, I want to offer some kind of Expression feature to my users, unless it is too much work, as this project is not the most important one and I don't have like 2 years to develop it. I think that the better thing I should do is to write something like CustomExpression that could be passed in parameters to some functions or subs.
Thanks for any suggestions that might bring me to my goal!
Interesting question. This is a language dependent feature, so I don't see this happening without some clever trickery of the IDE/compiler.
You could however have optional overloads on your Find method (vb.net is good for this), then make the search string manually to obtain the result.
Finally you could make use of lambda functions, but only in .net 3.5 and above. Even still, it would require your searcher to expose a preliminary set of data so you can recover the expression tree and build up the find string.
UPDATE
I've just been playing around with Reflection to see if I can retrieve the parameters passed, and build up a string dynamically depending on if they exist. This doesn't appear to be possible, due to the fact that compiled code doesn't reference the names.
This code just used was:
'-- Get all the "parameters"
Dim m As MethodInfo = GetType(Finder).GetMethod("Find")
Dim params() As ParameterInfo = m.GetParameters()
'-- We now have a reference to the parameter names, like Name and Description
Hmm. http://channel9.msdn.com/forums/TechOff/259443-Using-SystemReflection-to-obtain-parameter-values-dynamically/
Annoyingly it's not (easily) possible to recover the values sent, so we'll have to stick with building up the string in a non-dynamic fashion.
A simple optional method would look like:
Public Sub Find( _
Optional ByVal Name As String = "", _
Optional ByVal Description As String = "")
Dim query As String = String.Empty
If Not String.IsNullOrEmpty(Name) Then
query &= "Name=" & Name
'-- ..... more go here with your string seperater.
End If
End Sub

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