13

I'm bit confused on how async/await can work as parallel so I made a test code here.
I try to send 6 task I simulated with a list.
Each of this task will execute 3 other subtask:

You can copy/paste for test.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
         static void Main(string[] args)
        {
            //job simulation 
            Func<int, string, Tuple<int, string>> tc = Tuple.Create;
            var input = new List<Tuple<int, string>>{
                  tc( 6000, "task 1" ),
                  tc( 5000, "task 2" ),
                  tc( 1000, "task 3" ),
                  tc( 1000, "task 4" ),
                  tc( 1000, "task 5" ),
                  tc( 1000, "task 6" )
            };

            List<Tuple<int, string>> JobsList = new List<Tuple<int, string>>(input);

            //paralelism atempt
            List<Task> TaskLauncher = new List<Task>();

            Parallel.ForEach<Tuple<int, string>>(JobsList, item =>  JobDispatcher(item.Item1, item.Item2));

            Console.ReadLine();
        }
        public static async Task JobDispatcher(int time , string query)
        {
          List<Task> TList = new List<Task>();
          Task<string> T1 = SubTask1(time, query);
          Task<string> T2 = SubTask2(time, query);
          Task<string> T3 = SubTask3(time, query);
          TList.Add(T1);
          TList.Add(T2);
          TList.Add(T3);
          Console.WriteLine("{0} Launched ", query);

          await Task.WhenAll(TList.ToArray());

        
          Console.WriteLine(T1.Result);
          Console.WriteLine(T2.Result);
          Console.WriteLine(T3.Result);
      
        }


        public static async Task<string> SubTask1(int time, string query)
        {
            //somework
            Thread.Sleep(time);
            return query + "Finshed SubTask1";
        }
        public static async Task<string> SubTask2(int time, string query)
        {
            //somework
            Thread.Sleep(time);
            return query + "Finshed SubTask2";
        }
        public static async Task<string> SubTask3(int time, string query)
         {
             //somework
             Thread.Sleep(time);
             return query + "Finshed SubTask3";
         }


    }
}

Ideally at launch I should read:

task 1 launched
task 2 launched
task 3 launched
task 4 launched
task 5 launched
task 6 launched

Then at this point have all task running 6*3 = 18 thread running simultaneously, but it's not what happen here. Thing seem to execute synchronously.

Result is like:

Screenshot

What is the right way to write something that can launch task and subtask as 18 parallel thread with async/await?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Zwan
  • 632
  • 2
  • 6
  • 23
  • http://stackoverflow.com/a/11565317/2613020 – Sarvesh Mishra Feb 01 '16 at 09:12
  • It's not running synchronously, Task 4 is launched before Task 3 but finishes after Task 3. – Alexander Derck Feb 01 '16 at 09:15
  • first of all it should write in console before all since i await subtask later in the function – Zwan Feb 01 '16 at 09:30
  • Have a look at [these articles](https://docs.com/paulo-morgado/8860/async-await-general). – Paulo Morgado Feb 01 '16 at 12:18
  • @Zwan: `async`/`await` is about asynchrony (concurrency *without* threads); `Parallel` is about parallelism (concurrency by using *more* threads). These are completely different approaches to concurrency, and very very rarely do you need *both*. Perhaps if you describe what you're actually trying to do, we can suggest a more reasonable solution? – Stephen Cleary Feb 01 '16 at 13:09
  • well im pretty sure it have million way achieve what i do(so i try to made it using last tech).basicaly i use a foreach->usercontrol loaded in mainform i exec async usercontrol.methode for not freezing mainform . usercontrol.methode itself have to exec 3 other async usercontrol.methode (sql/acivedirectory/gui stuff etc.. ).that the main deal not freeze either Mainform or usercontrol and have everything work as parallele cuz it have lot to do for each usercontrol.Tough Mattew answer does the job. And it have a security reason if some methode are encapsuled in usercontrole. – Zwan Feb 01 '16 at 13:24
  • and how to do an @stephen here? lol won't work for me – Zwan Feb 01 '16 at 13:26

1 Answers1

17

Try this sample code. Note that it completes in around 6 seconds, which shows that all the tasks are run asynchronously:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            // ThreadPool throttling may cause the speed with which
            // the threads are launched to be throttled.
            // You can avoid that by uncommenting the following line,
            // but that is considered bad form:

            // ThreadPool.SetMinThreads(20, 20);

            var sw = Stopwatch.StartNew();
            Console.WriteLine("Waiting for all tasks to complete");

            RunWorkers().Wait();

            Console.WriteLine("All tasks completed in " + sw.Elapsed);
        }

        public static async Task RunWorkers()
        {
            await Task.WhenAll(
                JobDispatcher(6000, "task 1"),
                JobDispatcher(5000, "task 2"),
                JobDispatcher(4000, "task 3"),
                JobDispatcher(3000, "task 4"),
                JobDispatcher(2000, "task 5"),
                JobDispatcher(1000, "task 6")
            );
        }

        public static async Task JobDispatcher(int time, string query)
        {
            var results = await Task.WhenAll(
                worker(time, query + ": Subtask 1"),
                worker(time, query + ": Subtask 2"),
                worker(time, query + ": Subtask 3")
            );

            Console.WriteLine(string.Join("\n", results));
        }

        static async Task<string> worker(int time, string query)
        {
            return await Task.Run(() =>
            {
                Console.WriteLine("Starting worker " + query);
                Thread.Sleep(time);
                Console.WriteLine("Completed worker " + query);
                return query + ": " + time + ", thread id: " + Thread.CurrentThread.ManagedThreadId;
            });
        }
    }
}

Here's how you would use an array of tasks instead, in RunWorkers():

public static async Task RunWorkers()
{
    Task[] tasks = new Task[6];

    for (int i = 0; i < 6; ++i)
        tasks[i] = JobDispatcher(1000 + i*1000, "task " + i);

    await Task.WhenAll(tasks);
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • interesting way to build thread can i also add the 3 sub task in static async Task worker(int time, string query) methode? – Zwan Feb 01 '16 at 09:38
  • it react exactly as what i expect from parallele don't know yet exactly why some part of my code seem "bloking" when your's run smooth.will invesstigate later.anyway thanks will try this in production code – Zwan Feb 01 '16 at 10:14
  • thanks using the value time in first post your code is 6 seconde when mine 12 seconde proof something was not runing parallele at first ... – Zwan Feb 01 '16 at 10:20
  • Every single `Task.Run` call in this answer doesn't belong in here. The underlying operation is inherently asynchronous. You're accomplishing nothing by starting the asynchronous operation in a thread pool thread. – Servy Feb 01 '16 at 15:14
  • 1
    @Servy I removed the version that had the `Task.Run()`, but I believe it will still need the `Task.Run()` in `worker()`. (It previously had `await Task.Delay()` in it, but to make it more like the OP's code I've replaced it with `Thread.Sleep()`, which means that now it needs to be run in a Task.) – Matthew Watson Feb 01 '16 at 15:50
  • Matthew something i don't get is how to generate task from a foreach loop in your RunWorkers methode if i try add the task to a list thing become synchrone again any idea? – Zwan Feb 01 '16 at 16:14
  • @Zwan Does my latest edit help? I added an example at the end (using a `for` rather than a `foreach` loop, but it's almost the same). – Matthew Watson Feb 01 '16 at 16:25
  • yes it work now list work too i had a copy/paste abuse – Zwan Feb 02 '16 at 10:22
  • Just to mention, you dont need to await Task.Run(...) in worker() and also the method doesnt need to be async, that is just overhead because JobDispacher is already awaiting all workers – alecardv Nov 12 '18 at 13:41
  • 1
    I come from the future and I tried the code via WPF (swapped "Console" with "Debug"). I noticed that the "All tasks completed in..." didn't show up. Solution: Instead of `RunWorkers().Wait();` use `await RunWorkers ();` and add 'async' to the `Main` method. – Battle Dec 19 '19 at 08:49