Consider we have 2 I/O bound tasks that need to be processed, for N amount of elements. We can call the 2 tasks A and B. B can only be run after A has produced a result.
We can accomplish this in two ways. (Please ignore cases of Access to modified closure.)
Task.Run way:
List<Task> workers = new List<Task>();
for (int i = 0; i < N; i++)
{
workers.Add(Task.Run(async () =>
{
await A(i);
await B(i);
}
}
await Task.WhenAll(workers);
Classic Fork/Join:
List<Task> workersA = new List<Task>();
List<Task> workersB = new List<Task>();
for (int i = 0; i < N; i++)
{
workersA.Add(A(i));
}
await Task.WhenAll(workersA);
for (int i = 0; i < N; i++)
{
workersB.Add(B(i));
}
await Task.WhenAll(workersB);
Alternatively this can be done also in the following way:
List<Task> workers = new List<Task>();
for (int i = 0; i < N; i++)
{
workers.Add(A(i));
}
for (int i = 0; i < N; i++)
{
await workers[i];
workers[i] = B(i);
}
await Task.WhenAll(workers);
My concerns are that the following MSDN docs state that we should never use Task.Run for I/O operations.
Taking that into consideration, what's the best approach to handle this case then?
Correct me if I'm wrong, but we want to avoid using Task.Run, because we effectively queue Threads to handle the work, where if we just use await, there won't be any thread. (Due to the operations being I/O.)
I really wish to go down the Task.Run route, but if it ends up using threads for no apparent reason/does additional overhead, then It's a no-go.