I combined the comments from above in order to reach my solution. Indeed, I didn't need to use the async
or await
keywords at all. I merely had to create a list of tasks, start them all, then call WaitAll. Nothing need be decorated with the async
or await
keywords. Here is the resulting code:
public class MyClass
{
private int filesRead = 0;
public void Go()
{
string[] fileSystemEntries = Directory.GetFileSystemEntries(@"Path\To\Files");
Console.WriteLine("Starting to read from files! Count: {0}", fileSystemEntries.Length);
List<Task> tasks = new List<Task>();
foreach (var filePath in fileSystemEntries.OrderBy(s => s))
{
Task task = Task.Run(() => DoStuff(filePath));
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Finish! Read {0} file(s).", filesRead);
}
private void DoStuff(string filePath)
{
string fileName = Path.GetFileName(filePath);
string firstLineOfFile = File.ReadLines(filePath).First();
Console.WriteLine("[{0}] {1}: {2}", Thread.CurrentThread.ManagedThreadId, fileName, firstLineOfFile);
filesRead++;
}
}
When testing, I added Thread.Sleep
calls, as well as busy loops to peg the CPUs on my machine. Opening Task Manager, I observed all of the cores being pegged during the busy loops, and every time I run the program, the files are run in an inconsistent order (a good thing, since that shows that the only bottleneck is the number of available threads).
Every time I run the program, fileSystemEntries.Length
always matched filesRead
.
EDIT: Based on the comment discussion above, I've found a cleaner (and, based on the linked question in the comments, more efficient) solution is to use Parallel.ForEach
:
public class MyClass
{
private int filesRead;
public void Go()
{
string[] fileSystemEntries = Directory.GetFileSystemEntries(@"Path\To\Files");
Console.WriteLine("Starting to read from files! Count: {0}", fileSystemEntries.Length);
Parallel.ForEach(fileSystemEntries, DoStuff);
Console.WriteLine("Finish! Read {0} file(s).", filesRead);
}
private void DoStuff(string filePath)
{
string fileName = Path.GetFileName(filePath);
string firstLineOfFile = File.ReadLines(filePath).First();
Console.WriteLine("[{0}] {1}: {2}", Thread.CurrentThread.ManagedThreadId, fileName, firstLineOfFile);
filesRead++;
}
}
It seems there are many ways to approach asynchronous programming in C# now. Between Parallel
and Task
and async
/await
, there's a lot to choose from. Based upon this thread, it looks like the best solution for me is Parallel
, as it provides the cleanest solution, is more efficient than manually creating Task
objects myself, and does not clutter the code with async
and await
keywords while acheiving similar results.