Monday 13 April 2020

What keeps the Application running

Thinking a bit about the JavaScript Event Loop I realised of an interesting difference between 2 pieces of similar code in JavaScript - Node.js and .C# - Net.

Let's say I want a program that just does a http GET call and prints the retrieved string.

In JavaScript - Node we would write this:

async function getAndPrintText(){
    console.log("inside getAndPrintText");
    try{
  let response = await fetch("http://localhost:8080");
  if (!response.ok) {
   throw new Error('Network response was not ok');
  }
  let txt = await response.text();
  console.log("txt: " + txt);
 }
 catch(ex){
  console.log("something went wrong: " + ex.message);
 }
}

console.log("Started");
getAndPrintText();
console.log("After invoking GetAndPrintText");

//Output:
Started
inside getAndPrintText
After invoking GetAndPrintText
txt: "the returned html here"

Notice that I've not put an await for the getAndPrintText() call, so the "After invoking" is printed right after the call; before we obtain the result. That console.log is the last visible line in our script but anyway somehow the application waits for the http call to complete before it exits, how?
Well, it's the Event Loop. Node runs our script and then enters in the event loop to check for events coming from the asynchronous operations (and timeouts). The call to fetch() will be registered so that the event loop will be alive until it's completed.

The thing is a bit different in C# and .Net. Translating the above code to C# we would have:

    class Program
    {
        static HttpClient client = new HttpClient();

        static async Task GetAndPrintText()
        {
            Console.WriteLine("inside GetAndPrintText");
            try
            {
            HttpResponseMessage response = await client.GetAsync("http://localhost:8080/index.html");
            response.EnsureSuccessStatusCode();
            string responseBody = await response.Content.ReadAsStringAsync();

            Console.WriteLine("text: " + responseBody);
            }
            catch (Exception ex)
            {
                Console.WriteLine("something went wrong: " + ex.Message);
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Started");
            GetAndPrintText();
            Console.WriteLine("After invoking GetAndPrintText");
        }
}

Running the above the program will finish without printing the retrieve http content. The main application Thread finishes after doing the Console.WriteLine, and there's nothing to keep the application alive. A thread from the I/O TreadPool has been set to wait in the IOCP for the client.GetAsync to complete, but this is a background thread, so it won't keep the application alive if the main Thread reaches its end. You can read more about IOCP in this previous post

For the application to wait for the http call to complete we have to add an await in our Main (and turn it into an async Main), like this:

        static async Task Main(string[] args)
        {
            Console.WriteLine("Started");
            await GetAndPrintText();
            Console.WriteLine("After invoking GetAndPrintText");
        }

I already talked about async Main in the past. The main application Thread will be waiting in the hidden, compiler-generated application entry function for our async Main to complete, and our async Main is waiting for the http call. That's how the application is kept alive, nothing to do with an Event Loop as in Node.js

No comments:

Post a Comment