The release of .NET Core 3 last month includes a brand new
IAsyncEnumerable interface in the System.Collections.Generic namespace. The Microsoft documentation simply states,
Exposes an enumerator that provides asynchronous iteration over values of a specified type.
This new interface sounds interesting, so I looked at the code in my
open-source assembly to see if I could implement it somewhere. I chose the code for LoadFiles() in the DirectoryHelper class that loads files from a collection of directories. The code that looks like this,
- public static IEnumerable<FileInfo> LoadFiles(IEnumerable<DirectoryInfo> directories, string searchPattern, SearchOption searchOption)
- {
- var files = new List<FileInfo>();
-
- foreach (var directory in directories.Where(directory => directory.Exists).Select(directory => directory))
- {
- files.AddRange(directory.EnumerateFiles(searchPattern, searchOption));
- }
-
- return files.AsEnumerable();
- }
Using LoadFiles() is simple.
- foreach (var file in DirectoryHelper.LoadFiles(searchFolders, "*.*", SearchOption.AllDirectories))
- {
-
- }
With some changes, I turned LoadFiles() into a new method called LoadFilesAsync(). The code now looks like this,
- public static async IAsyncEnumerable<IEnumerable<FileInfo>> LoadFilesAsync(IEnumerable<DirectoryInfo> directories, string searchPattern, SearchOption searchOption)
- {
- var options = new EnumerationOptions() { IgnoreInaccessible = true };
-
- if (searchOption == SearchOption.AllDirectories)
- {
- options.RecurseSubdirectories = true;
- }
-
- var dirs = directories.Where(directory => directory.Exists).Select(directory => directory);
-
- foreach (var directory in dirs)
- {
- var files = await Task.Run(() => directory.EnumerateFiles(searchPattern, options));
- yield return files;
- }
- }
As you can see above, I changed the return type from,
IEnumerable<FileInfo>
To,
async IAsyncEnumerable<IEnumerable<FileInfo>>
Then I wrapped the call to directory.EnumerateFiles() in a Task.Run(). After getting the files in each directory, I yield back the collection of files to the calling method. So, using LoadFilesAsync() is slightly different using the new await foreach() in .NET Core 3.
- await foreach (var files in DirectoryHelper.LoadFilesAsync(searchFolders, "*.*", SearchOption.AllDirectories))
- {
- foreach (var file in files)
- {
-
- }
- }
Each time the files are returned from the call to EnumerateFiles(), when it hits the yield, it returns that list back to the calling code for processing. That way the calling code show above will be able to iterate over the list of files, while LoadFilesAsync() loads the next list of files. Now lest test the performance.
Performance
I wanted to see if one of these methods were more performant than the other.
As you can see, for the number of files tested with my benchmark test, LoadFilesAsync() is 5.44ms faster and allocates 101,143 byes less of memory. Make sure to benchmark your own code the implements IAsyncEnumerable.
Summary
I hope that you will check out this new interface in .NET Core 3 for use in your projects. If you have any comments or questions, please make them below.