1

I've found a question that was really useful for me, but I still can't realize what equivalent in LINQ-world has further construction:

public async Task<List<ObjectInfo>> GetObjectsInfo(string[] objectIds)
{
    var result = new List<ObjectInfo>(objectIds.Length);
    foreach (var id in objectIds)
    {
        result.Add(await GetObjectInfo(id));
    }

    return result;
}

If I wrote instead

var result = await Task.WhenAll(objectIds.Select(id => GetObjectInfo(id)));

wouldn't these tasks be started simultaneously? In my case it would be better to run them in series.

Edit 1: Answering to the comment of Theodor Zoulias.

Of course, I forgot Async suffix in methods' names!

The method GetObjectInfoAsync makes http request to external service. Additionally, this service has restriction for requests' frequency, so I use following construction.

            using (var throttler = new Throttler(clientId))
            {
                while (!throttler.IsCallAllowed(out var waitTime))
                {
                    await Task.Delay(waitTime);
                }

                var response = await client.PerformHttpRequestAsync(request);
                return response.Content.FromJson<TResponse>(serializerSettings);
            }

Throttler knowns last request's time for each client.

MYriad
  • 118
  • 7
  • 1
    Possible duplicate: https://stackoverflow.com/questions/20422026/run-sequence-of-tasks-one-after-the-other – Sweeper Dec 05 '19 at 06:39
  • 1
    All that we know is that a bunch of `Task`s will be created synchronously. We could assume that they will be created in a hot state (in other words already started), and that their creation will be fast enough to be considered simultaneous. But we need to see the code of `GetObjectInfo` to be sure. Which BTW should be probably named `GetObjectInfoAsync`. – Theodor Zoulias Dec 05 '19 at 06:48
  • 1
    So the tasks are hot, since they are created by an async method. The creation of each task involves the creation of a `Throttler` object, and calling its `IsCallAllowed` method. Both of these can be potentially blocking. For example the `IsCallAllowed` may need to query the database. When creating multiple of these tasks by using the `Select` method and passing the resulting enumerable to the `Task.WhenAll` method, these delays add up. In that case saying that the tasks are started *simultaneously* may not be accurate. – Theodor Zoulias Dec 05 '19 at 08:07
  • @TheodorZoulias, fortunately this method accesses a static ConcurrentDictionary object. – MYriad Dec 05 '19 at 16:36
  • 1
    In that case the line `await Task.WhenAll(objectIds.Select(id => GetObjectInfo(id)));` will start all tasks practically simultaneously indeed. And also `await` all of them concurrently. – Theodor Zoulias Dec 05 '19 at 16:41

1 Answers1

3

You should take into account the following considerations when you are using whenAll

Quotes taken from MS documentation

If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.

If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.

If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.

If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion state before it's returned to the caller.

That means that while the items will run maybe simultaneously (you can't know how as it depends on the resources of the machine and you can't know the order), the above can't be avoided.

If you want to have specific error handling per case and a specific order, then the first solution is the way to go. It kind of beats the point of the async/await but not in it's entirety. Even with sequential execution of the async items, your thread at least will not go to sleep and can still be used until the awaitable is ready.

Community
  • 1
  • 1
Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
  • So from the point of view of tasks' execution (without taking into considiration errors and exceptions) the code block with foreach is almost equivalent to the one with WhenAll? Because it creates in short period of time amount of tasks, which will be processed according to system scheduler - just like in WhenAll construction? – MYriad Dec 05 '19 at 16:51
  • It's not. The for statement with the await keyword, will run the tasks sequentially. So if the sequence is irrelevant, the when all is better, as it will end faster. – Athanasios Kataras Dec 05 '19 at 17:04
  • Sequentially means that the next task will be started only after previous completion? – MYriad Dec 06 '19 at 06:10
  • That's exactly it. – Athanasios Kataras Dec 06 '19 at 06:11
  • Returning to my first question - what equivalent with such behaviour available in linq constructions? – MYriad Dec 06 '19 at 06:12
  • 1
    There is none under the Task class. Even Microsoft documentation has pretty much the same example as you. You could use a for each on the list but it shouldn't make much difference. – Athanasios Kataras Dec 06 '19 at 06:16