Home > Mobile >  Implementing async and await without async and await
Implementing async and await without async and await

Time:05-18

For some tutorial I'm planning to explain async and await using some callbacks. Based on https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/async I tried to implement something similar using callbacks:

static void Main(string[] args)
{
    Console.WriteLine("Lets start a delicious breakfast");

    FryEggsCallback(2, () =>
    {
        Console.WriteLine("eggs are ready");
    });
    FryBaconCallback(3, () =>
    {
        Console.WriteLine("bacon is ready");
    });
    ToastBreadCallback(2, x =>
    {
        ApplyButter(x);
        ApplyJam(x);
        Console.WriteLine("toast is ready");
    });
    Console.WriteLine("Breakfast is ready!");
}

static void FryEggsCallback(int count, Action onReady) 
{
    Task.Delay(3000).Wait(); // simulate some work that should be done
    onReady();
}
static void FryBaconCallback(int count, Action onReady)
{
    Task.Delay(3000).Wait();
    onReady();
}
static void ToastBreadCallback(int count, Action<Toast> onReady)
{
    Task.Delay(3000).Wait();
    onReady(new Toast());
}

However the Task.Delay().Wait() blocks my thread, so instead of having the three calls running asynchronously, they run sequentially one by one.

How would I go about implementing the three calls asynchronously with callbacks instead of using async and await?

CodePudding user response:

It's not Task.Delay(x) that's blocking, it's Task.Delay(x).Wait(). Task.Wait will synchronously block.

I'd instead chain a continuation on the Task.Delay task using Task.ContinueWith, so that onReady() executes asynchronously when the task completes, i.e.:

Task.Delay(3000).Wait();

...should become:

Task.Delay(3000).ContinueWith(x => onReady());

CodePudding user response:

Note that Task.Delay is just a wrapper around a timer object. So if want to have callbacks when the task is done your example essentially boils down to:

new Timer (o => Console.WriteLine("eggs are ready"), null, 3000, Timeout.Infinite);
new Timer (o => Console.WriteLine("bacon is ready"), null, 3000, Timeout.Infinite);

new Timer (o => {
    var toast = new Toast();
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
}, null, 3000, Timeout.Infinite);
Console.WriteLine("Breakfast is ready!")

An obvious problem is that the breakfast is ready before any of the components. But perhaps more importantly, it does not explain how async rewrites the method to a statemachine. So I'm really not sure what the example is supposed to teach.

Consider a somewhat simplified example from your link:

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");

Console.WriteLine("Breakfast is ready!");

To simulate this behavior we can use a statemachine that keeps track of how far we have reached in the preparation, and what components we have:

public Egg FriedEgg;
public Toast ToastedBread;
private int state = 0;

public void Run(){
    switch(state){
        case 0;
        if(ToastedBread != null){
            ApplyButter(ToastedBread )   
            ApplyJam(ToastedBread );
            Console.WriteLine("Toast is ready");
            Juice oj = PourOJ();
            Console.WriteLine("Oj is ready");
            state = 1;
            Run(); // Continue to the next state if possible
        }
        break;
       case 1:
       if( FriedEgg != null){
          Console.WriteLine("Eggs are ready");
            state = 2;
            Run(); // Continue to the next state if possible
       }
       break;
       case 2:
           Console.WriteLine("Breakfast is ready!");
           state = 3;
       break;
    }
}

Each of the timer callbacks would set the corresponding Egg or Toast field and call Run. So regardless if the eggs or toast being done first it will continue with the preparation as far as it can. Note that this is just to demonstrate the concept, and it lacks things like thread safety.

And as you might see this behind the scene work is rather complex, and it is not even going into things like synchronizationContexts.

  • Related