John
John

Reputation: 4601

how to add more details to a Task returned by HttpClient.GetAsync

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;     
    }

Upvotes: 5

Views: 980

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456917

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.

Upvotes: 7

Related Questions