0

I'm trying to get my head around this and I think I know what's happenig, but just can't get something to work. I'd appreciate any help. Basically my demo code is to make a cup of coffee where I want the boil the kettle fuunction to run async as it takes a long time. I have put an await on MakeACupOfCoffee as I don't want the stopwatch timing until the entire process has finished. This is finishing early and I know why, but I've no idea how to make the code wait for the kettle to boil while allowing everything else to continue.

Thanks for any pointers.

Here's my code..

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

namespace asyncawait
{
    class Program
    {
        static void Main(string[] args)
        {
            TimeACoffee();
        }

        private static async Task TimeACoffee()
        {
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();

            // MakeACupOfCoffee completes before WaitForTheKettleToBoil has finished, 
            // therefore the await doesn't behave as I want it to
            await MakeACupOfCoffee();

            stopwatch.Stop();
            Console.WriteLine($"\n\nTime taken : {stopwatch.Elapsed}");

            Console.ReadLine();
        }

        private static async Task MakeACupOfCoffee()
        {
            // This function shouldn't return until everything has completed
            // I can't await here otherwise it'd be pointless doing asynchronously
            WaitForTheKettleToBoil();
            GetCup();
            AddCoffee();
            AddMilk();
            AddSugar();
        }

        private static Task WaitForTheKettleToBoil()
        {
            return Task.Run(() =>
            {
                Console.WriteLine("Waiting for the kettle to boil...");
                Thread.Sleep(8000);
            });
        }

        private static void AddMilk()
        {
            Console.WriteLine("Adding milk");
            Thread.Sleep(100);
        }

        private static void AddSugar()
        {
            Console.WriteLine("Adding sugar");
            Thread.Sleep(100);
        }

        private static void GetCup()
        {
            Console.WriteLine("Getting cup");
            Thread.Sleep(100);
        }

        private static void AddCoffee()
        {
            Console.WriteLine("Adding coffee");
            Thread.Sleep(100);
        }
    }
}
  • What do you mean with "*I can't await here otherwise it'd be pointless doing asynchronously*"? Probably you just want to start every task and await them all? Does [this](https://stackoverflow.com/q/18310996/2441442) help? – Christian Gollhardt Jan 11 '19 at 02:19
  • 1
    Go async all the way and also avoid thread.sleep. Use Task.Delay – Nkosi Jan 11 '19 at 02:21
  • 3
    Please [edit] your question title to something that is actually descriptive. *asynch-await* is in a tag and therefore is redundant, and *recommendations* isn't a meaningful word out of context. Your title should be clear and descriptive enough to be of use to a future reader who is skimming through a list of search results. – Ken White Jan 11 '19 at 02:22
  • Wasn't sure if to hammer this but so far this falls into https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await?rq=1 – Nkosi Jan 11 '19 at 02:24
  • Maybe you could use something like `Task.WaitAll` in your `MakeACupOfCoffee` function, as you would like to run all your preparation task in parallel but you need all of them finished to have a complete cup of coffee. https://learn.microsoft.com/de-de/dotnet/api/system.threading.tasks.task.waitall?view=netframework-4.7.2 – royalTS Jan 11 '19 at 07:04
  • Possible duplicate of [How and when to use ‘async’ and ‘await’](https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await) – Styx Jan 11 '19 at 08:10

3 Answers3

4

Your program (letting async propagate fully)

Note : As this is only a test program, this is only an exmaple solution, obviously you might not need to await anything.

However, since this is an async question , we can await a Task.Delay, also since C# 7.x we can use the async main method

private static async Task TimeACoffee()
{
   var stopwatch = new System.Diagnostics.Stopwatch();
   stopwatch.Start();

   await MakeACupOfCoffee();

   stopwatch.Stop();
   Console.WriteLine($"\n\nTime taken : {stopwatch.Elapsed}");

   Console.ReadLine();
}

private static async Task MakeACupOfCoffee()
{
   await WaitForTheKettleToBoil();
   await GetCup();
   await AddCoffee();
   await AddMilk();
   await AddSugar();
}

private static async Task WaitForTheKettleToBoil()
{
   Console.WriteLine("Waiting for the kettle to boil...");
   await Task.Delay(100);
}

private static async Task AddMilk()
{
   Console.WriteLine("Adding milk");
   await Task.Delay(100);
}

private static async Task AddSugar()
{
   Console.WriteLine("Adding sugar");
   await Task.Delay(100);
}

private static async Task GetCup()
{
   Console.WriteLine("Getting cup");
   await Task.Delay(100);
}

private static async Task AddCoffee()
{
   Console.WriteLine("Adding coffee");
   await Task.Delay(100);
}

Alternatively you could do this

private static async Task MakeACupOfCoffee()
{

   // get the cup first, why not?
   await GetCup();

   // when this is all completed you have a coffee, though coffee drinkers may not like the order.
   await Task.WhenAll(WaitForTheKettleToBoil(), AddCoffee(), AddMilk(), AddSugar());
}

Alternatively you could also do this

private static void WaitForTheKettleToBoil()
{
   Console.WriteLine("Waiting for the kettle to boil...");
}

private static async Task MakeACupOfCoffee()
{
   // start the task (yehaa)
   var waitForTheKettleToBoilTask = Task.Run(WaitForTheKettleToBoil);

   // need a cup before we can add anything to it
   await GetCup();

   // when this is all completed you have a coffee
   await Task.WhenAll( AddCoffee(), AddMilk(), AddSugar(), waitForTheKettleToBoilTask);
}

Note : people who like the coffee milk and sugar in first might not like you


Comment from ckuri

Which might make the differences a little clearer than i did

It should be noted that your codes behave vastly different. In your first code you boil, wait for it to complete, get a cup, wait for it to complete, add coffee, wait, add milk wait and add sugar and wait. However, in your alternatives, you get the cup, wait until you got it, and then you start boiling, adding coffee, milk and sugar all at the same time and then wait until all finished

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • You can actually remove async from all the action methods and just return the task – Nkosi Jan 11 '19 at 02:30
  • @Nkosi too right, no need for the extra state machine – TheGeneral Jan 11 '19 at 02:31
  • Wouldn't wait `GetCup` until `WaitForTheKettleToBoil` is finished in this case? – Christian Gollhardt Jan 11 '19 at 02:32
  • 1
    @ChristianGollhardt more aligned with the results wanted now, with various options – TheGeneral Jan 11 '19 at 02:41
  • It should be noted that your codes behave vastly different. In your first code you boil, wait for it to complete, get a cup, wait for it to complete, add coffee, wait, add milk wait and add sugar and wait. However, in your alternatives, you get the cup, wait until you got it, and then you start boiling, adding coffee, milk and sugar all at the same time and then wait until all finished. – ckuri Jan 11 '19 at 06:53
  • @ckuri i added your comment if you dont mind, as it makes it fairly clear – TheGeneral Jan 11 '19 at 06:57
  • I would just add the "async" suffix on any async method https://learn.microsoft.com/en-us/previous-versions/hh191443(v=vs.140)#BKMK_NamingConvention – Gonzo345 Jan 11 '19 at 07:13
1

If you want to keep your cup fetching and adding stuff non-async which is perfectly fine if these are fast operations, but keep the kettle boiling while you do all this, you would just need to save the boiling task and return it:

public static Task MakeACupOfCoffee()
{
  var boilTask = WaitForKettleToBoil();
  GetCup();
  AddCoffee();
  AddMilk();
  AddSugar(); 
  return boilTask;
}

Now, the boiling (task) starts, and while it’s boiling you get your cup, add coffee, milk and sugar and finally return the boil task. So when you await MakeACupOfCoffee it will wait until the kettle finished boiling the water.

ckuri
  • 3,784
  • 2
  • 15
  • 17
0

if you cannot use async, why don't use Task.wait?

for example,

var t = Task.Run( () => {
  // Action on here
} );

t.wait();

it'll stop task while the action has finished.

Arphile
  • 841
  • 6
  • 18