September 8, 2024

Thread.Delay() -- a first look at async/await

Here is the code:
using System;
using System.Threading.Tasks;

namespace Example
{
    public class Wally
    {
        public static int Delay { private get; set; }

        public static void old_wait ()
        {
            Thread.Sleep ( Delay );
        }

        public static void wait ()
        {
            var t = Task.Run ( async delegate
            {
                await Task.Delay ( Delay );
                return 1776;
            } );
            t.Wait();
        }

        public static void chat ( string msg, int repeat )
        {
            for ( int i=0; i<repeat; i++ ) {
                Console.WriteLine ( msg );
                Wally.wait ();
            }
        }
    }
}

namespace HogHeaven
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Example.Wally.Delay = 1000;
            Example.Wally.chat ( "Kilroy is here !!", 4 );
            Console.WriteLine ( "All done" );
        }
    }
}
// THE END
This does the same thing as our two delay examples (based on Task.Sleep()) and yields the same output. We are back on track now (as much as we are ever on track). The story now is that we are playing with Task.Delay() rather than Task.Sleep(). I'll admit that I am just taking code from here: There is plenty to talk about. First I'll mention that I have parked the old call to Task.Sleep() in a method called old_wait(). Old code never dies -- it just gets commented out or parked in a never gets called method with "old" somewhere in its name. Also worth noting is that the anonymous delegate routine returns a silly value "1776" that we just ignore.

What the heck is a "delegate?" -- I hear you asking.
Certain methods in various API expect to have functions passed to them as arguments, and setting up a function as a delegate (or a delegate as a function) is the way to do this. Also you'll have to pardon me for talking about "functions" as if we are C programmers. What I should be saying is: Certain methods in various API expect to have methods passed to them as arguments, and setting up a method as a delegate (or a delegate as a method) is the way to do this. The case in point here is the call to Task.Run which needs to be given some thing to run, whatever you want to call it. If you look at the documentation for Task.Run() you will see that "Run()" is an overloaded method that offers you at least a half dozen ways to call it.
Here is the method rewritten as a lambda. Instead of that big ugly word "delegate", you get some interesting syntax instead

public static void wait ()
        {
            var t = Task.Run ( async () =>
            {
                await Task.Delay ( Delay );
                return 1776;
            } );
            t.Wait();
        }

However we end up calling Task.Run(), we aren't really using async/await in a useful and productive way, but we are using it. If we are going to Delay a task, we need a task to delay, so we run one. Then we use t.Wait() to synchronize with the task. If we were doing this right, we would do something useful and then synchronize with it, but this is just example code and we don't.

Now the word "async" is worth talking about. It describes a method (in this case a delegate) in just the same way as descriptive words like "public", "void", "static" (always static) get slapped in front of method names. This is an anonymous "method", so there is no method name, but you get the idea. What "async" does is to simply get the C# compiler in the right mood to recognize an "await" keyword that will appear later in the function. That is the honest truth. An "await" doesn't even have to come along. The compiler will be in the right mood, but be "disappointed" perhapss, but no harm will come in that case. Most of the excitement is built into the await keyword. Apparently this was done because "await" wasn't part of the original C# language and they need to allow the word "await" to be used for any and all purposes in the normal case -- to avoid breaking old code that might possibly use it for some other purpose (like a variable name or method name). If you try to use await in any method that isn't decorated with "async", the compiler will call a foul.

There is more to the "async" word than just setting up a new domain for the compiler in which the word "await" can be recognized. The async method needs to follow certain conventions. It is not at all obvious from this example, but the async method should return an object of type Task (or a generic derivative of that type). In the general case, the async method will magically take on a new life running in a new task. This is not at all obvious in this example pulled from the Microsoft example.

So here we have two threads (for what looks like no particular reason) with one thread waiting for the other and the other waiting via the "await" for the Task.Delay. However, take a look at the next section before coming to any conclusions about all of that.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org