0

For some tutorial I'm planning to explain async and await using some callbacks. Based on https://learn.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?

9
  • 2
    Task.Delay(3000).ContinueWith(task => onReady());?` Commented May 18, 2022 at 13:56
  • 1
    async/await isn't callbacks. Instead of explaining it, you'll confuse people. With async/await awaiting inside a loop is no different than awaiting outside it. With just callbacks, it's a nightmare Commented May 18, 2022 at 14:05
  • The key concept that replaces callbacks is the Task, not async/await. A Task is neither a thread nor a function. It's a promise that something will complete and maybe produce a value in the future. That promise may execute in a threadpool thread, it may represent an IO operation that will be signaled by the OS on completion, or it may be signaled by a timer or another thread. await works on that promise, not the original call. You could say that the code after await is a callback on that Promise Commented May 18, 2022 at 14:08
  • 2
    Eric Lippert wrote a great series on Continuation Passing Style (and showing why you'd really want your programming language to hide it all away from you) in the build up to the announcement of async in C#. Unfortunately, I'm failing to locate it currently. Without the syntax sugar, it looks really messy, so don't expect it to give anybody new to the subject any great insight. Commented May 18, 2022 at 14:10
  • This means that the question's code is very different from how tasks and async/await work. The key abstraction, the Promise, is missing from callbacks. In .NET Framework 4, before async/await were introduced, people used continuations with ContinueWith that called delegates when the Promise/Task completed. You can consider that code as a callback to the Promise Commented May 18, 2022 at 14:13

2 Answers 2

2

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());
Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.