The Powerful Async Pattern in .NET 1.1 without TPL and Async CTP1

Hi, today I would like to show you Async Pattern for .NET 1.1 and C#. I prepared training workshop on this subject and this is one of my very good examples. So, below I will show you an example test code of entire async versus sync performance measurement solution. You may wonder how that is possible in .NET 1.1 without the Task Parallel Library and without async keyword? The answer is very simple. Everything you need is a delegate. If I should say what was the biggest success of .NET 1.1 I would say that it was a delegate keyword in C#. So let me show you the very cool pattern for creating Async invocation without Tasks in .NET 1.1. That is light and can be implemented in every .NET versions above than 1.1. The picture on the right with four CPUs can show you that this pattern has very good scalability and light impact for systems. And as every good patter it can be very easy to understood.

namespace AsyncInvocationTest
{
  using System;
  using System.Diagnostics;
  using System.Globalization;
  using System.Runtime.CompilerServices;
  using System.Threading;

  class InvocationTest
  {
    int status;
    int maxThreads;

    public InvocationTest(int status, int maxThreads) {
      this.status = status;
      this.maxThreads = maxThreads;
    }

    [MethodImpl(MethodImplOptions.Synchronized)]
    private int Increment(int arg)
    {
      lock (this) {
        status += arg;
        int newStatus = status;
        return newStatus;
      }      
    }

    private int Invocation(int arg)
    {
      // Slow part - internal slow calculation.
      CpuTimeConsumption();
      // Fast part - synchronized for sure.
      return Increment(arg);
    }

    #region Helpful Propery and Method

    public int Status {
      get { return status; }
    }

    private void CpuTimeConsumption() {
      for (int i = 0; i < int.MaxValue / 400; i++)
        ;
    }

    #endregion

    #region Classic Sync Invocation

    public int[] Run(int count) {
      int[] result = new int[count];

      for (int i = 0; i < count; i++) {
        int arg = i + 1;
        result[i] = Invocation(arg);
      }

      return result;
    }

    #endregion

    #region Powerful Async Invocation Pattern

    private delegate int InvocationDelegate(int arg);

    public int[] RunAsync(int count) {
      int[] result = new int[count];

      InvocationDelegate invoker = new InvocationDelegate(Invocation);

      IAsyncResult[] results = new IAsyncResult[count];
      WaitHandle[] waits = new WaitHandle[maxThreads];

      for (int i = 0; i < count; ) {
        for (int t = 0; t < maxThreads && i < count; t++, i++) {
          int arg = i + 1;
          IAsyncResult asyncResult = invoker.BeginInvoke(arg, null, null);
          results[i] = asyncResult;
          waits[t] = asyncResult.AsyncWaitHandle;
        }

        WaitHandle.WaitAll(waits);
      }

      for (int i = 0; i < count; i++) {
        try {
          result[i] = invoker.EndInvoke(results[i]);
        } catch {
          result[i] = default(int);
        }
      }

      return result;
    }

    #endregion
  }

  class Program
  {
    #region Invocation Performance Test

    enum TestKind
    {
      Sync = 0,
      Async = 1
    }

    static int[] TestInvoker(TestKind kind,
                            int startStatus,
                            int count,
                            int concurrentThreads /*max == 64*/) {
      InvocationTest invoker = new InvocationTest(startStatus, concurrentThreads);

      int[] result = null;
      Stopwatch st = Stopwatch.StartNew();
      if (kind == TestKind.Async)
        result = invoker.RunAsync(count);
      else
        result = invoker.Run(count);
      st.Stop();

      Console.WriteLine(
        "Invoker end status {0}, {1:0000} ms {2:00} threads, invocation {3}.",
        invoker.Status,
        st.Elapsed.TotalMilliseconds,
        concurrentThreads,
        Enum.GetName(typeof(TestKind), kind).ToLower());

      return result;
    }

    static void CheckResults(int[] rSync, int[] rAsync) {
      if (rSync.Length != rAsync.Length) {
        Console.WriteLine("Error in results.");
        return;
      }
      else {
        int[] rSyncSorted = new int[rSync.Length];
        int[] rAsyncSorted = new int[rAsync.Length];

        Array.Copy(rSync, rSyncSorted, rSync.Length);
        Array.Copy(rAsync, rAsyncSorted, rAsync.Length);

        Array.Sort(rSyncSorted);
        Array.Sort(rAsyncSorted);

        int[] rSyncChanges = new int[rSync.Length];
        int[] rAsyncChanges = new int[rAsync.Length];

        for (int i = rSyncSorted.Length - 1; i > 0; i--)
        {
          rSyncChanges[i] = rSyncSorted[i] - rSyncSorted[i-1];
          rAsyncChanges[i] = rAsyncSorted[i] - rAsyncSorted[i-1];
        }

        rSyncChanges[0] = rSyncSorted[0];               
        rAsyncChanges[0] = rAsyncSorted[0];

        Array.Sort(rSyncChanges);
        Array.Sort(rAsyncChanges);

        int sumSync = 0;
        int sumAsync = 0;
        for (int i = 0; i < rSyncChanges.Length; i++)
        {
          sumSync += rSyncChanges[i];
          sumAsync += rAsyncChanges[i];
        }

        if (sumSync != sumAsync)
        {
          Console.WriteLine("ERROR!");
          return;
        }
      }
    }

    #endregion

    static void Main() {
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

      for (int i = 0; i < 10; i++) {
        int[] r1 = TestInvoker(TestKind.Sync, 1, 128, 1);
        int[] r2 = TestInvoker(TestKind.Async, 1, 128, (int)((i + 1) * 6.4));
        CheckResults(r1, r2);
      }

      Console.WriteLine("Press any key to continue...");
      Console.ReadKey();
    }
  }
}

And here is a result of invoke this performance test on the slow PC but with 4 logical CPU.

Invoker end status 8257, 1397 ms 01 threads, invocation sync.
Invoker end status 8257, 0660 ms 12 threads, invocation async.
Invoker end status 8257, 1153 ms 01 threads, invocation sync.
Invoker end status 8257, 0571 ms 19 threads, invocation async.
Invoker end status 8257, 1088 ms 01 threads, invocation sync.
Invoker end status 8257, 0563 ms 25 threads, invocation async.
Invoker end status 8257, 1118 ms 01 threads, invocation sync.
Invoker end status 8257, 0561 ms 32 threads, invocation async.
Invoker end status 8257, 1056 ms 01 threads, invocation sync.
Invoker end status 8257, 0531 ms 38 threads, invocation async.
Invoker end status 8257, 1052 ms 01 threads, invocation sync.
Invoker end status 8257, 0531 ms 44 threads, invocation async.
Invoker end status 8257, 1061 ms 01 threads, invocation sync.
Invoker end status 8257, 0528 ms 51 threads, invocation async.
Invoker end status 8257, 1049 ms 01 threads, invocation sync.
Invoker end status 8257, 0538 ms 57 threads, invocation async.
Invoker end status 8257, 1056 ms 01 threads, invocation sync.
Invoker end status 8257, 0532 ms 64 threads, invocation async.
Press any key to continue...

Ok, so what is the async pattern? You can found it in the region I named Powerful Async Invocation Pattern :). My first conclusion is that when you using a delegate BeginInvoke and EndInvoke methods you have clean code and very good scalability. As you can see results are 2 times faster when I using this pattern because I have two physical CPUs not four (two of them are disabled). So, scalability is very good. We improved invocation time about half time faster. And second conclusion of this patter is that it is very easy to understood. Third conclusion is about using of concurrent threads. As you can see fastest time of invocation was when we used 51 concurrent thread for this test solutions, but in every tests invocation time was similar. And at last I have fourth conclusion that is when you must using .NET 1.1 you can designing and developing solutions without TPL and Async. This is the power of the delegate object. And you probably see right now how easy you can connect with web service many times async? Everything you need is calling Web Service client method in method you use by delegate than Powerful Async Invocation Pattern gives you all you need :).

Regards,

P ;).

4 Replies to “The Powerful Async Pattern in .NET 1.1 without TPL and Async CTP1”

  1. Pingback: The Powerful Async Pattern in .NET 2.0 with Threads « .NET Rules! Blog

  2. Pingback: The Powerful Async Pattern in .NET 4.0 with Tasks « .NET Rules! Blog

  3. Pingback: The Powerful Async Pattern in .NET 4.0 with Parallel « .NET Rules! Blog

  4. Pingback: The Powerful Async Pattern in .NET and C# 5.0 with Async CTP1 « .NET Rules! Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.