TaskT_SyncAsync_Extensions.IterT is eager
See original GitHub issueIEnumerable<Task<T>>.IterT
extension method is eager. Is it expected? Is this related to #564?
This can lead to unintentional iteration (in some cases creation and start) of all tasks in a sequence. Here’s a small snippet that’d show an extreme case of the issue. In less extreme cases it may lead to uncontrolled parallelism which goes unnoticed.
public class Program
{
public static void ConsoleLog(string str) => Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: {str}");
public static IEnumerable<Task<int>> GetNumbersAsync()
{
int i = 0;
while (true) // Mimic infinite (or just very long sequence of data)
yield return GetNumberAsync();
async Task<int> GetNumberAsync()
{
var value = Interlocked.Increment(ref i);
if (value % 100_000 == 0) // I'm not printing every single task to avoid spamming the screenshot and to show how many tasks can be created in just a few seconds
ConsoleLog($"Starting {nameof(GetNumberAsync)} Task #{value}");
await Task.Delay(1000); // Mimic some network/disk IO
return value;
}
}
public static async Task Main(string[] args)
{
ConsoleLog("Starting the application");
await GetNumbersAsync()
.IterT(value =>
{
ConsoleLog($"Value: {value}");
Thread.Sleep(500); // Mimic some CPU intensive work
});
}
}
Because the enumeration of the GetNumbersAsync is eager it’ll create hundreds of thousands of tasks that start right away in the background. If GetNumbersAsync would query some service instead we would bomb the service with hundreds of thousands requests.
In this example application managed to spawn 3.2kk tasks while it was able to process only 10 values.
More information on the extension method:
namespace: LanguageExt
class: TaskT_SyncAsync_Extensions
method signature: public static Task<Unit> IterT<A>(this IEnumerable<Task<A>> ma, Action<A> f)
Issue Analytics
- State:
- Created 4 years ago
- Comments:15 (13 by maintainers)
Top GitHub Comments
@louthy, @gwintering, @bender2k14 Thanks for the effort you guys put into this issue (and the entire library - it rises C# to a new level ) 😃
I’m not sure there’s any need. The behavior is a nested fold with a unit state:
Iter
is about side-effects, it’s unknown what those side-effects are going to be. If the tasks are truly using IO then who’s to say how much throughput any one machine could do in parallel? The .NET scheduler may not be as effective as you’d like in all situations, butIterT
can’t make that judgement. It’s not waiting for results, it’s firing an forgetting - which is kind of implied by the fact thatIter
returnsUnit
. And so it will try to generate as many tasks as it can and let the .NET scheduler deal with it.If it did anything else then I’d argue it wouldn’t be iteration. It has no mandate to slow down, or do cunning stuff like wait for
n
tasks to complete before moving on.WindowIter
is explicit in its behaviour and I think that’s the correct approach.