C# Asynchronous, Multi Threaded Development... Digging In. Part II

In this article we'll dig a bit deeper into what is actually happening when we use the asynchronous calls using C#. Just as in my previous article (here), we'll simulate a long running I/O bound process as if we were fetching records from a database, making a web service call or reading or writing from a file and see how we can write more performant code by letting the main thread continue on and not wait for the results. 

Part I. Async Calls and Threads.

The core idea behind this architecture is that we don't want execution threads hanging out and doing nothing (and even getting so bored that they fall sleep) while waiting for another process, especially an i/o (input/output) bound long-running process.  I'll have two different methods that take a while to execute in order to simulate a long-running process to demonstrate how the async technique will work.  We'll have two differnt method signatures so you can see it will work regardless of the signature of the method we are calling.

Here are our two "fake" long-running i/o bound processes:

    public static class SlowPoke
    {
        public static int GetValueSlowly()
        {
            // DON'T EVER DO THIS! DEMO PURPOSES ONLY!
            Console.WriteLine("Fetching value now..."); // simulation of long running i/o bound process
            Thread.Sleep(150);
            return 1;
        }

        public static int GetValueSlowly(int input)
        {
            return GetValueSlowly() + 10;
        }
    }

The best way to see how everything works is to download the sample code and step through it with the thread debug window running.  Step into the project then you can bring up the threading debug window by going to the "Debug" menu and choosing "Windows" and then "Threads".

We can give running threads names in order to distinguish them in an easy way.  Also, threads all have a unique identifier assigned by the runtime (which is different than id assigned by Windows).  There are a couple of things that we need to get started:

By naming the thread, we make it easier to track in the dev environment:

Thread.CurrentThread.Name = "MAIN THREAD: ";

In order to use the delegate.BeginInvoke() we need to define a couple method signatures. We'll define methods that return something and are either passed nothing or passed something else (you gotta love generics).

    public delegate TOutput MethodCall<TOutput>();
    public delegate TOutput MethodCall<TOutput, TInput>(TInput input);

When we lanch the debugger, from the main thread, we're executing the following code, which will execute ald allow the main thread to move on, not waiting for a response, because we are using BeginInvoke() which keeps our main thread from hanging out (and/or falling asleep) waiting for the SlowPoke.GetValueSlowly() to execute. I'll use the same pattern I described in my previous atricle, passing the delegate as the state parameter of BeginInvoke().

MethodCall<Int32, Int32> asyncMethodCall = SlowPoke.GetValueSlowly;
asyncMethodCall.BeginInvoke(5, EndSlowMethod, asyncMethodCall);
Console.WriteLine("Done in main thread.\n\n");

Let's walk through step by step.  On the first line, we create the delegate for our long-running i/o process (often these calls are already built into the objects we are using within the framework).  On the second line, we call BeginInvoke() on the method, passing the input parameter for the target method, along with the callback method and a reference to the delegate (so we can pull out the value later).  Finally, in the last line we write to the console. 

When we call BeginInvoke() using the delegate, the responsibility for executing the called method is passed to the .NET ThreadPool.  The ThreadPool will manage threads in the most efficient way to get the work done so we don't have to worry about it.  We should never start a new thread for a specific task (Richter rule #1: borrow em' don't buy em').  Threads in the pool operate at a lower priority and wlll work in the background until our long running i/o process is complete and then call the callback method.

Meanwhile the main thread has continued chugging through our code. At this point, if our main thread leaves the context of the Main() method, our program is done and the unloading process begins which makes sense if you think about it.  The main thread is responsible for managing the whole process be it drawing the UI in a windows app or handling requests in a middle tier dll.  If no one is using the dll/app it makes sense not to care about any of the requests made while it was in use, right?  In order to prevent our program exiting (remember… we are still waiting on a response from our async method call), we block the man thread by calling  Console.ReadLine() which stops the thread and waits for user input.  This is something we would never normally do without a specific purpose in mind and our app would keep humming away as the user pokes at different buttons generating different requests.

When we run our sample code, what we see on the screen is:

Starting with thread MAIN THREAD:  ID=9...

Done in main thread.

And we are now hanging out waiting for the other process to run.  Now the ThreadPool is managing the execution of our async call which means we now have a multi-threaded application with out explicitly calling one ThreadStart() and our app can scale with the number of CPUs on the box.  If you are like me and think about writing code to explicitly handle a 64 core processor, it would make you shudder – this is by far an easier approach and if you like better results for much less coding -- let the ThreadPool handle it.

Now a thread from the thread pool calls our async function:

public static void EndSlowMethod(IAsyncResult result)
{

    Debugger.Break();

    // This is the delegate pointing to the method calles asynchronously
    MethodCall<int, int> slowMethod = result.AsyncState as MethodCall<int, int>;

    // working with ThreadPool thread here
    Console.WriteLine(string.Format("Working with thread {0} ID={1}...",
        Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId));

    int value = slowMethod.EndInvoke(result);
    Console.WriteLine("Result retrieved with value=" + value);
}

If you are in studio, looking at the thread debug window, you'll notice the arrow indicating which thread is running the code is no longer the thread that we have marked as "MAIN THREAD". And as we run we have reaced the end of our run cycle and have the following from the console (your thread ID values could be different, but everything else should remain the same):

Starting with thread MAIN THREAD:  ID=10...

Done in main thread.

Fetching value now...

Working with thread  ID=7...

Result retrieved with value=11

So to sum up, we have written a async callback function where the main thread is free to continue chugging away while we wait for a long running i/o bound process to return (if it ever chooses to.. if not, that's fine because our app has not hung).  Very cool stuff.

Part II.  Cool trick

I got the next trick straight from the Richter seminar during devscovery and it is very (very) cool.  We can take advantage of .NET 2.0 anonymous methods to do all of this inside one method in our source code and it will behave exactly the same. Notice how we no longer have to pass a reference to the delegate using this method.

public static void AnonymousMethodSample()
{
    Console.WriteLine(string.Format("Starting with thread {0} ID={1}...",
        Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId));

    MethodCall<Int32> asyncMethodCall = SlowPoke.GetValueSlowly;

    asyncMethodCall.BeginInvoke(delegate(IAsyncResult result)
    {
        Debugger.Break();

        // working with ThreadPool thread here
        Console.WriteLine(string.Format("Working with thread {0} ID={1}...",
            Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId));

        int value = asyncMethodCall.EndInvoke(result);
        Console.WriteLine("Result retrieved with value=" + value);
    }, null);

    Console.WriteLine("Done in main thread\n\n");
}

I hope you enjoyed this article... Check out the source code and run through it a few times and if you have any questions, let me know because I'd love to dig deeper into this subject.  Next article we'll start looking at how to make asynchronous calls to actual long running i/o bound calls (not just faking it anymore) and then later on I'll cover wiring all of this up to the front end in a windows or web ui and maybe even a web service and we'll take a look at how to write some highly scalable code.

Until next time,
Happy coding


Similar Articles