Following code is retrieving content from several url asynchronously, and as soon as one content has been downloaded thanks to Task.WhenAny, then it's processed. But in the processed part, I need the Identifier object. I think it's clearer to show you the code:
 var downloadTasks = new List<Task<string>>();
        foreach (var identifier in input.Identifiers)
        {
            string url = BuildUrl(identifier, input.PeriodInYear, input.Interval);
            var data = _webRequest.GetData(url, token);
            downloadTasks.Add(data); // Here I only add the data, but not the Identifier. I thought about using a List<Tuple<Identifier, Task<string>>, but then I can't use it with Task.WhenAny(...)
        }
        while (downloadTasks.Count > 0)
        {
            var finishedDownloadTask = await Task.WhenAny(downloadTasks);
            downloadTasks.Remove(finishedDownloadTask);
            foreach (var content in await finishedDownloadTask)
            {
                // hereI I also need the Identifier object here !
            }
        }
Here the code of GetData:
public virtual async Task<string> GetData(string uri, CancellationToken token)
    {
        // log removed
        // try catch removed
        string result = string.Empty;
            using (var client = new HttpClient())
            using (var response = await client.GetAsync(uri, token).ConfigureAwait(false))
            {
                if (response.IsSuccessStatusCode)
                    result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                else
                    logger.Error("Unable to retrieve data from the following url: {0} - StatusCode: {1}", uri, response.StatusCode);
            }
        return result;     
    }
                The HTTP request is sent out, and HttpClient. GetAsync returns an uncompleted Task .
C# HttpClient POST form data var url = "https://httpbin.org/post"; using var client = new HttpClient(); var data = new Dictionary<string, string> { {"name", "John Doe"}, {"occupation", "gardener"} }; var res = await client. PostAsync(url, new FormUrlEncodedContent(data)); var content = await res. Content.
GetAsync(Uri, HttpCompletionOption) Send a GET request to the specified Uri with an HTTP completion option as an asynchronous operation. GetAsync(Uri, CancellationToken) Send a GET request to the specified Uri with a cancellation token as an asynchronous operation.
Returns. The task object representing the asynchronous operation.
I don't think that the "build a list of tasks", "await Task.WhenAny", "remove completed task from list" approach is very clean.
I find that my code is usually cleaner when I step back, take a look at the code, and write a new asynchronous method that does the "initial" processing as well as the "postprocessing". With your example, it would look something like this:
async Task GetDataAndPostProcessAsync(Identifier identifier, CancellationToken token)
{
  var url = BuildUrl(identifier, input.PeriodInYear, input.Interval);
  var content = await _webRequest.GetDataAsync(url, token);
  // Use 'content' with 'identifier'
}
...
var tasks = input.Identifiers.Select(identifier =>
    GetDataAndPostProcessAsync(identifier, token)).ToList();
await Task.WhenAll(tasks);
I cover this in more detail in recipe 2.6 of my Concurrency in C# Cookbook.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With