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