I've been trying to improve my understanding and usage of async
code in C#, specifically how to integrate it into existing synchronous code.
I have the following test program, which is basically the test from https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/start-multiple-async-tasks-and-process-them-as-they-complete?pivots=dotnet-6-0 with a synchronous caller and a LinqPad runnable wrapper.
void Main()
{
var a = new A();
List<string> urls = new List<string>()
{
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=net-6.0",
"https://stackoverflow.com/questions/11836325/await-operator-can-only-be-used-within-an-async-method"
};
a.GetUrlContentLengths(urls).Dump();
}
public class A
{
public int GetUrlContentLengths(IEnumerable<string> urls)
{
return Task.Run<int>(async() => await GetUrlContentLengthsAsync(urls)).Result;
}
public async Task<int> GetUrlContentLengthsAsync(IEnumerable<string> urls)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
IEnumerable<Task<int>> downloadTasksQuery = urls.Select(x => ProcessUrlAsync(x, client));
var downloadTasks = downloadTasksQuery.ToList();
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
return total;
}
public async Task<int> ProcessUrlAsync(string url, System.Net.Http.HttpClient client)
{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
}
This linked document describes the O(n²) issues like this:
What we’ve effectively created here is an O(N2) algorithm: for each task, we search the list for the task to remove it, which is an O(N) operation, and we register a continuation with each task, which is also an O(N) operation
So would this minor change to a Dictionary
fix this and leave the whole thing as an O(n) operation?
public async Task<int> GetUrlContentLengthsAsync(IEnumerable<string> urls)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
IEnumerable<Task<int>> downloadTasksQuery = urls.Select(x => ProcessUrlAsync(x, client));
var downloadTasks = downloadTasksQuery.ToDictionary(xk => xk.GetHashCode(), xv => xv);
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks.Values);
downloadTasks.Remove(finishedTask.GetHashCode());
total += await finishedTask;
}
return total;
}