2

So I know Task.Run basically makes the code run in a new thread from the thread pool. But what is the difference if I do something like this:

var tasks = new List<string>(){ "foo", "bar" }
   .Select(e => Task.Run(async () => await Process(e)))
   .ToList();

await Task.WhenAll(tasks);

vs

var tasks = new List<string>(){ "foo", "bar" }
   .Select(async e => await Process(e))
   .ToList();

await Task.WhenAll(tasks);

My understanding would be that the second snippet only runs one at a time while it awaits some async process, and the first one actually runs them in parallel on threads from the thread pool?

BineG
  • 365
  • 3
  • 9
  • 1
    You're specifying `async` but you're not doing `await` anywhere. The code will give you a build warning. – Sean Mar 06 '20 at 11:47
  • To get a good understanding, I suggest you add to your test program's `Process()` method a line of code `Console.WriteLine(Thread.CurrentThread.ManagedThreadId);` and watch the output of both versions. The second version will give a compile warning, and will indeed be synchronous. – Matthew Watson Mar 06 '20 at 11:47
  • 1
    does this answer your question? [is using an an `async` lambda with `Task.Run()` redundant?](https://stackoverflow.com/questions/32591462/is-using-an-an-async-lambda-with-task-run-redundant) – Patrick Beynio Mar 06 '20 at 11:49
  • @Sean I forgot to include the await. I corrected the question. – BineG Mar 06 '20 at 12:04
  • @PatrickBeynio thank you. So if the intent is to trigger parallel execution of async methods the first method would indeed be correct? – BineG Mar 06 '20 at 12:05
  • 1
    As a side note, according to the [guidelines](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap#naming-parameters-and-return-types) the `Process` method should be named `ProcessAsync`. – Theodor Zoulias Mar 06 '20 at 12:15
  • 1
    yes, otherwise they would still get executed after each other. – Patrick Beynio Mar 06 '20 at 12:37

1 Answers1

2

Both of these approaches instantiate a list of tasks, and then await them to complete. Normally there should be no difference between them, unless the ProcessAsync method returns already completed tasks (unless it runs synchronously in other words). For example the implementation bellow:

async Task ProcessAsync(string arg)
{
     Thread.Sleep(1000); // Simulate some heavy calculation
     await Task.CompletedTask;
}

...blocks the current thread for 1000 msec and then returns a completed task. This implementation will run in parallel only with the first approach (with Task.Run), and will run sequentially with the second approach (without Task.Run). The reason is that creating the task is expensive (all the work is done during the creation), and awaiting the task is cheap (the task has already been completed). With Task.Run you essentially offload the expensive work to the ThreadPool threads.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104