I have an IEnumerable< Task< MyObject>> that effectively describes how to fetch a collection of items from cache. The MyObject element has an error message and a success object. I would like to do something like this:
List<MyObject> fetchAll(IEnumerable<Task<MyObject>> tasks)
{
var (firstResponse, restOfEnumerable) = await tasks.DoSomethingToGetFirstResonseAndRestOfEnumerableAsync().ConfigureAwait(false);
if(!firstResponse.IsSuccess)
{
return null;
}
List<MyObject> ret = new List<MyObject>(firstResponse.Result);
ret.AddRange(await Task.WhenAll(restOfEnumerable).ConfigureAwait(false));
return ret;
}
I know I could do this by calling ToList() on the enumerable and doing this
List<MyObject> fetchAll(List<Task<MyObject>> tasks)
{
var firstResponse = await tasks[0].ConfigureAwait(false);
if (!firstResponse.IsSuccess)
{
return null;
}
List<MyObject> ret = new List<MyObject>(firstResponse.Result);
ret.AddRange(await Task.WhenAll(tasks.Skip(1)).ConfigureAwait(false));
return ret;
}
but I'm trying to not iterate the IEnumerable fully just to test the first item.
I suppose I could also drop down and get the enumerator myself like this
List<MyObject> fetchAll(IEnumerable<Task<MyObject>> tasks)
{
var ret = new List<MyObject>();
using (var enumerator = tasks.GetEnumerator())
{
if (enumerator.MoveNext())
{
var firstResult = await enumerator.Current.ConfigureAwait(false);
if (!firstResponse.IsSuccess)
{
return null;
}
ret.Add(firstResponse.Result);
}
List<Task> restOfTasks = new List<Task>();
while (enumerator.MoveNext())
{
restOfTasks.Add(enumerator.Current);
}
ret.AddRange(await Task.WhenAll(restOfTasks).ConfigureAwait(false));
}
}
but I'm hoping there is something built in that I'm overlooking.
As mentioned in the question, I do not want to fully iterate the enumerable to test the first element and I do not want to cause a multiple enumeration of the enumerable (it may not always be possible to enumerate multiple times or it could be very expensive).
This is what I ended up doing - seems you have to drop down to the enumerator, there's no way to pass around a partially enumerated enumerable that I could figure out.
List<MyObject> fetchAll(IEnumerable<Task<MyObject>> tasks)
{
var ret = new List<MyObject>();
using (var enumerator = tasks.GetEnumerator())
{
if (enumerator.MoveNext())
{
var firstResult = await enumerator.Current.ConfigureAwait(false);
if (!firstResponse.IsSuccess)
{
return ret;
}
ret.Add(firstResponse.Result);
// if there are more, then get all the tasks and use Task.WhenAll to start them all at once and wait for them all to finish
if (enumerator.MoveNext())
{
// also, we only create this task list if there are more
var restOfTasks = new List<Task<MyObject>>();
restOfTasks.Add(enumerator.Current);
while (enumerator.MoveNext())
{
restOfTasks.Add(enumerator.Current);
}
results.AddRange(await Task.WhenAll(restOfTasks).ConfigureAwait(false));
}
}
}
return ret;
}