49

I have a method which does 2 independent pieces of logic. I was hoping I can run them both at the same time .. and only continue afterwards when both those child methods have completed.

I was trying to get my head around the async/await syntax but I just don't get it.

Here's the code:

public PewPew SomeMethod(Foo foo)
{
    var cats = GetAllTheCats(foo);
    var food = GetAllTheFood(foo);

    return new PewPew
               {
                   Cats = cats,
                   Food = food
               };
}

private IList<Cat> GetAllTheCats(Foo foo)
{
    // Do stuff, like hit the Db, spin around, dance, jump, etc...
    // It all takes some time.
    return cats;
}

private IList<Food> GetAllTheFood(Foo foo)
{
    // Do more stuff, like hit the Db, nom nom noms...
    // It all takes some time.
    return food;
}

So with that code above, I want to say : go and get all the cats and food at the same time. Once we're finished, then return a new PewPew.

I'm confused because I'm not sure which classes above are async or return a Task, etc. All of em? just the two private ones? I'm also guessing I need to leverage the Task.WaitAll(tasks) method, but I'm unsure how to setup the tasks to run at the same time.

Suggestions, kind folks?

Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
  • 5
    `Foo foo` will be shared between threads. Make sure you [lock](http://msdn.microsoft.com/library/c5kehkcz.aspx) correctly. – Corak May 24 '13 at 06:10

5 Answers5

64

Here is what you may want to do:

public async Task<PewPew> SomeMethod(Foo foo)
{
    // get the stuff on another thread 
    var cTask = Task.Run(() => GetAllTheCats(foo));
    var fTask = Task.Run(() => GetAllTheFood(foo));

    var cats = await cTask;
    var food = await fTask;

    return new PewPew
               {
                   Cats = cats,
                   Food = food
               };
}

public IList<Cat> GetAllTheCats(Foo foo)
{
    // Do stuff, like hit the Db, spin around, dance, jump, etc...
    // It all takes some time.
    return cats;
}

public IList<Food> GetAllTheFood(Foo foo)
{
    // Do more stuff, like hit the Db, nom nom noms...
    // It all takes some time.
    return food;
}

There are two things you need to understand here:

  1. What is diff between this:

    var cats = await cTask; var food = await fTask;

And this:

Task.WaitAll(new [] {cTask, fTask});

Both will give you similar result in the sense let the 2 async tasks finish and then return new PewPew - however, difference is that Task.WaitAll() will block the current thread (if that is UI thread, then UI will freeze). instead, await will break down the SomeMethod say in a state machine, and return from the SomeMethod to its caller as it encounters await keyword. It will not block the thread. The Code below await will be scheduled to run when async task is over.

  1. You could also do this:

    var cats = await Task.Run(() => GetAllTheCats(foo)); var food = await Task.Run(() => GetAllTheFood(foo));

However, this will not start the async tasks simultaneously. Second task will start after the first is over. This is because how the await keyword works.

EDIT: How to use SomeMethod - somewhere at the start of the call tree, you have to use Wait() or Result property - OR - you have to await from async void. Generally, async void would be an event handler:

public async void OnSomeEvent(object sender, EventArgs ez) 
{ 
  Foo f = GetFoo();
  PewPew p = await SomeMethod(f);
}

If not then use Result property.

public Foo2 NonAsyncNonVoidMethod() 
{
   Foo f = GetFoo();
   PewPew p = SomeMethod(f).Result; //But be aware that Result will block thread
  
   return GetFoo2(p);
}
starball
  • 20,030
  • 7
  • 43
  • 238
YK1
  • 7,327
  • 1
  • 21
  • 28
  • 2
    `await cTask` and `await fTask` wouldn't that wait for the first then the second ? I mean is it going to be parallel ? – Dimitar Dimitrov May 24 '13 at 06:17
  • 2
    yes, wait will be sequential, however, tasks have been started in parallel. its not different than task.WaitAll() in terms of time taken to wait. – YK1 May 24 '13 at 06:22
  • 3
    @YK1 soz buddy - i'm confused. Are you saying that even though you do `var cats = await cTask;` and `var food = await fTask` .. both those tasks will *run at the same time* (async) and not one -at a time- (sync). – Pure.Krome May 24 '13 at 06:24
  • 5
    @Pure.Krome yes, tasks will start running when you say Task.Run() and not when you say await. – YK1 May 24 '13 at 06:25
  • 2
    @YK1 i also get a compile error :( `The return type of an async method myst be void, Task or Task` .. and this is for the method `SomeMethod`.. – Pure.Krome May 24 '13 at 06:36
  • 2
    @Pure.Krome: Change it to Task – YK1 May 24 '13 at 06:38
  • 2
    which then breaks all my existing calls to this method. – Pure.Krome May 24 '13 at 06:42
  • 2
    Actually, its not very logical - but reason is here: http://stackoverflow.com/questions/7010904/why-return-type-of-async-must-be-void-task-or-taskt – YK1 May 24 '13 at 06:44
  • 2
    Somewhere at the start of the call tree you either have to accept the task and use Result property instead of await. OR - you have to await from a async void. – YK1 May 24 '13 at 06:46
  • Kewl. so then I would do this? (Also, can u update your answer). `var someMethodTask = someMethod(foo); var pewPew = someMethodTask.Result;` – Pure.Krome May 24 '13 at 07:08
  • 2
    @YK1 well I just tried it and they run in sequence, most certainly. – Dimitar Dimitrov May 24 '13 at 07:16
  • 2
    @DimitarDimitrov: what did you try await on task variable or await on Task.Run() ? Read my answer carefully. Also, you can add a Console.WriteLine between two awaits and in the tasks. – YK1 May 24 '13 at 07:24
  • 2
    @YK1 `await cTask, await fTask` (one after another), first one finishes unboxes the task and the second one starts. Maybe I'm doing something wrong, not sure. – Dimitar Dimitrov May 24 '13 at 07:26
  • 2
    I don't know why this is marked as the answer. It doesn't even compile. And if you change it to compile properly, it runs the tasks sequentially, not in parallel. – Matthew Watson May 24 '13 at 07:43
  • 2
    @MatthewWatson Assuming we're looking at the same version, then it compiles fine for me and should execute in parallel. – svick May 24 '13 at 09:00
  • 2
    @svick The thing I tried was the part (2) "You could also do this", which seems to operate sequentially. I did finally get the first part to compile by adding all the missing classes, and that *does* work. – Matthew Watson May 24 '13 at 09:12
  • 2
    @MatthewWatson Yeah, but the answer specifically says that part won't run in parallel. – svick May 24 '13 at 09:13
  • 2
    @svick Yeah true; I wasn't reading it carefully enough! This is indeed a correct answer. :) – Matthew Watson May 24 '13 at 09:14
  • @YK1 **...However, this will not start the async tasks simultaneously. Second task will start after the first is over...** I never knew about it. Can you please share a link for this please? – Zameer Ansari Jan 25 '16 at 16:15
  • @YK1 Can you please give a suggestion for thread safety about [my question](http://stackoverflow.com/a/35344029/2404470) – Zameer Ansari Feb 12 '16 at 10:53
  • I just have to say, i have been studying up on async programming for a few weeks now racking my brain on how to loop async calls into non-async code currently in existence and this is the first most comprehensive and easy to understand example of executing non-async method asynchronously. It works beautifully for me and you have helped me avoid updating a bunch of code! Thx! – Nugs Aug 03 '17 at 21:52
  • instead of line 4 and 5 from the first code, you can write await Task.WhenAll(cTask , fTask); – amal50 Dec 21 '18 at 21:00
  • How do you debug this – John Nyingi Jul 04 '19 at 07:36
  • you can also wrap the other Task.Run and awaits in a Task.Run( async () => { /*code here*/ }); – OrangeKing89 Nov 12 '19 at 20:18
29

By far the easiest way to do this is to use Parallel.Invoke()

IList<Cat> cats;
IList<Food> food;

Parallel.Invoke
(
    () => cats = GetAllTheCats(foo),
    () => food = GetAllTheFood(foo)
);

Parallel.Invoke() will wait for all the methods to return before it itself returns.

More information here: http://msdn.microsoft.com/en-us/library/dd460705.aspx

Note that Parallel.Invoke() handles scaling to the number of processors in your system, but that only really matters if you're starting more than just a couple of tasks.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    like Task.WaitAll(), Parallel.Invoke() will block calling thread. This is unlike await. – YK1 May 24 '13 at 06:50
  • 1
    @YK1 Indeed but the op asked "I was hoping I can run them both at the same time .. and only continue afterwards when both those child methods have completed." which this does, of course. – Matthew Watson May 24 '13 at 07:33
  • 1
    @MatthewWatson: yes, but he also said, `head around the async/await syntax` - so i've compared between non-blocking await and blocking Wait() – YK1 May 24 '13 at 07:47
  • 2
    @YK1 Aye, but he doesn't need the async, and you answer above doesn't actually run the tasks in parallel - it runs then sequentially. – Matthew Watson May 24 '13 at 07:48
  • 1
    @MatthewWatson: it will depend which version you use. await on Task.Run() will run sequesntial. But running both Task.Run() before first await will start tasks in parallel – YK1 May 24 '13 at 07:50
  • 2
    @YK1 Which is why it's much easier to use `Parallel.Invoke()` like I said. – Matthew Watson May 24 '13 at 07:56
  • 1
    @MatthewWatson: true its easy - but it blocks (on UI thread, it will freeze UI) - await is much better because non-blocking - wont freeze UI. I understand it is tad difficult to understand. – YK1 May 24 '13 at 08:00
  • 2
    @YKI1 It's not difficult to understand (and stop trying to be facetious!). But your answer doesn't demonstrate running two threads in parallel while also awaiting it in the UI without blocking. Anyway, that's all I'm going to say. :) – Matthew Watson May 24 '13 at 08:08
  • @MatthewWatson: I am not facetious at all - in fact i had to look up that word on google. As per my understanding, one of the main design goals of await keyword is to wait for async tasks in UI thread without blocking UI thread. Whole WinRT API is designed on this concept. – YK1 May 24 '13 at 08:13
  • 1
    @YK1 Yep, the first part of your answer does work (I +1ed it) Seems I was trying an incorrect version at first. – Matthew Watson May 24 '13 at 09:13
12

You don't have to use async if you're not in an async method or you're using an older version of the .Net framework.. just use Tasks for simplicity:

Task taskA = Task.Factory.StartNew(() => GetAllTheCats(foo));
Task taskB = Task.Factory.StartNew(() => GetAllTheFood(foo));

Task.WaitAll(new [] { taskA, taskB });
// Will continue after both tasks completed
Adam Tal
  • 5,911
  • 4
  • 29
  • 49
  • @AdamTal - when to use Tasks and when Async? Does both run methods simultaneously? – Zameer Ansari Jun 15 '15 at 11:02
  • 1
    @zameeramir - async is a compiler implementation of tasks running. Use async when you can but when you're not in an async context you can simply use a Task for running something in a different thread. – Adam Tal Jun 15 '15 at 15:33
  • @AdamTal Your words are too geeky. Can you simplify them. [This entire page](http://stackoverflow.com/questions/16728338) is crunching my mind – Zameer Ansari Jan 25 '16 at 16:51
  • @student I wrote it as simple as I can. You seem to miss some knowledge if it's still not clear. Try reading about async and await (https://msdn.microsoft.com/en-us/library/hh191443.aspx) and about tasks (http://www.codeproject.com/Articles/189374/The-Basics-of-Task-Parallelism-via-C) – Adam Tal Jan 25 '16 at 18:50
  • How do you stop them if you run them infinitely – John Nyingi Jul 04 '19 at 13:25
  • @JohnNyingi you'd have to pass a [CancellationToken](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-cancel-a-task-and-its-children) to the methods. – McGuireV10 Jan 11 '20 at 15:14
1

You can use the TPL to wait for multiple tasks while they are running. See here.

Like this:

public PewPew SomeMethod(Foo foo) {
    IList<Cat> cats = null;
    IList<Food> foods = null;

    Task[] tasks = new tasks[2] {
        Task.Factory.StartNew(() => { cats = GetAllTheCats(foo); }),
        Task.Factory.StartNew(() => { food = GetAllTheFood(foo); })
    };

    Task.WaitAll(tasks);

    return new PewPew
               {
                   Cats = cats,
                   Food = food
               };
}
Maarten
  • 22,527
  • 3
  • 47
  • 68
1

Adding to the other answers, you could do something like:

public PewPew SomeMethod(Foo foo)
{
    Task<IList<Cat>> catsTask = GetAllTheCatsAsync(foo);
    Task<IList<Food>> foodTask = GetAllTheFoodAsync(foo);

    // wait for both tasks to complete
    Task.WaitAll(catsTask, foodTask);

    return new PewPew
    {
        Cats = catsTask.Result,
        Food = foodTask.Result
    };
}

public async Task<IList<Cat>> GetAllTheCatsAsync(Foo foo)
{
    await Task.Delay(7000); // wait for a while
    return new List<Cat>();
}

public async Task<IList<Food>> GetAllTheFoodAsync(Foo foo)
{
    await Task.Delay(5000); // wait for a while
    return new List<Food>();
}
Dimitar Dimitrov
  • 14,868
  • 8
  • 51
  • 79