Skip to content

Latest commit

 

History

History
131 lines (108 loc) · 6.09 KB

Asynchronous.md

File metadata and controls

131 lines (108 loc) · 6.09 KB

#Asynchronous Methods

Sometimes you will need to call a method which puts a heavy load on a local processor and takes a significant amount of time before it returns.

The following code:

  • displays "About to start calculation"
  • Hangs (non-responsive, blocked), for three seconds, whilst it simulates running a, locally, computationally heavy process
  • displays "Calculation has finished"
  • displays "Returned from calculation with result: 42"
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("About to start calculation");
        int data = GetHeavyComputeCycleData();
        Console.WriteLine("Returned from calculation with result : " + data.ToString());
        Console.ReadLine();
    }

    static int GetHeavyComputeCycleData()
    {
        DateTime waitUntil = DateTime.Now.AddSeconds(3);
        while (DateTime.Now < waitUntil) { }
        Console.WriteLine("Calculation has finished");
        return 42;
    }

}

We will now improve the code, to remove that "Hangs (non-responsive, blocked)". It’s actually a console application to make the example as easy to understand as possible. Imagine it’s in a Windows or Web application and we are keeping the UI responsive.

C# has a await operator which lets the caller attach a continuation. C# also has a Task class, with a Run method, which runs a task on a different thread, helping keep the current one responsive.

We need to add the async keyword to methods that have calls to await. (This prevents potentially breaking old c# code, which may have used await as a variable name. It also adds a lot of code to the generated IL, e.g. to setup a state machine).

We can’t mark a entry point, such as main, async, so we will need a wrapper for our compute heavy routine. Lets call that wrapper GetHeavyComputeCycleDataWrapper()

The following, improved, code :

  • displays "About to start calculation"
  • displays "Returned from calculation with result currently unknown - still computing"
  • Is not blocked, we could be running code in main thread, actually we just wait for user to press Enter
  • Approximately three seconds later the calculation finishes, on its separate thread, the continuation is activated, code execution go’s back to where it was when calculation started
  • displays "Returned from calculation with result: 42"
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("About to start calculation");
        GetHeavyComputeCycleDataWrapper();
        Console.WriteLine("Returned from calculation with result currently unknown - still computing");
        Console.ReadLine();
    }

    static async void GetHeavyComputeCycleDataWrapper()
    {
        int data =  await Task<int>.Run( ( )=> GetHeavyComputeCycleData());
        Console.WriteLine("Returned from calculation with result : " + data.ToString());
    }

    static int GetHeavyComputeCycleData()
    {
        DateTime waitUntil = DateTime.Now.AddSeconds(3);
        while (DateTime.Now < waitUntil) { }
        return 42;
    }

}

The above is a good solution for locally compute heavy calls. However often the delay is because we are waiting for a API call on a separate process, likely separate server, too complete. This delay is unpredictable e.g. because of network conditions and current API load.

We dont need to run the code on a separate thread here. But we will definitely benefit from using await to attach a continuation.

The following code:

  • displays "About to make API call"
  • displays "API call was made; we may still be waiting for result though"
  • Is not blocked, we could be running code in main thread, actually we just wait for user to press Enter
  • Approximately five seconds later, the API returns its result, our continuation is activated
  • displays "API call is now returning"
 class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("About to make API call");
        GetNonBlockingAPIResultWrapper();
        Console.WriteLine("API call was made, we may still be waiting for result though");
        Console.ReadLine();
    }

    static async Task GetNonBlockingAPIResultWrapper()
    {
        int data = await GetNonBlockingAPIResult();
        Console.WriteLine("slow network API call returned with : " + data.ToString());
    }

    static async Task<int> GetNonBlockingAPIResult()
    {
        await Task.Delay(5000);
        Console.WriteLine("API call is now returning");
        return 42;
    }

}

The above samples are designed to be simple to illustrate what’s happening. They are not production ready, e.g. no exception handling code!

What is a continuation? what’s actually happening above? Well it’s all on one thread. Dotnet is creating a state machine in the background to implement the above flow.

Maybe Wikipedia will help with understanding: "First-class continuations are a language's ability to completely control the execution order of instructions. They can be used to jump to a function that produced the call to the current function, or to a function that has previously exited. One can think of a first-class continuation as saving the execution state of the program. It is important to note that true first-class continuations do not save program data – unlike a process image – only the execution context. This is illustrated by the "continuation sandwich" description:

Say you're in the kitchen in front of the refrigerator, thinking about a sandwich. You take a continuation right there and stick it in your pocket. Then you get some turkey and bread out of the refrigerator and make yourself a sandwich, which is now sitting on the counter. You invoke the continuation in your pocket, and you find yourself standing in front of the refrigerator again, thinking about a sandwich. But fortunately, there's a sandwich on the counter, and all the materials used to make it are gone. So you eat it. :-)[4]"

For more information, here is a great video, "the zen of asynch Best practices for best performance"