Click here to Skip to main content
Click here to Skip to main content

Learn How to Simplify the Asynchronous Programming Model

, 19 Aug 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
An article that desribes the concepts of the APM and the use of features to simplify its use.

Preface

This article is not meant, by any means, as a substitute for MSDN’s articles about the Asynchronous Programming Model and how to simplify the use of this programming model by using the C# 2.0 Iterator language feature. The CLR guru Jeffrey Richter has written a series of articles about how to use iterators, anonymous methods, and lambda expressions in order to simplify using the APM. These articles show the operations as to how the APM allows you to use a very small number of threads to execute a large amount of work without blocking any of the threads.

The purpose of the first part of this article is to focus on code that uses iterators to simplify the APM. Later on, this article will focus on splitting your code into callback methods. Throughout this article, several Wintellect code examples will be used in order to help the reader see the symmetry behind using the APM model and the features that simplify its use. For this reason, the first section of this paper will describe the C# 2.0 iterator language feature in order to show how it can be used to simplify using the APM.

As has been noted, using the APM is difficult because you have to split code into callback methods. For example, in one method, you initiate an asynchronous read or write operation, and then you have to implement another method that will get called (possibly by a different thread) to process the result of the I/O operation. The many useful language constructs of C#, such as the try/catch/finally statements, the lock statement, the using statement, and even the loops (for, while, and foreach) cannot be split up across multiple methods. You also have to avoid having state data reside on the thread stack, because data on the thread stack cannot migrate to another thread. So, the first code example will not exemplify the APM, but it will show how an iterator operates. While this topic is not covered in this article, it is the hope of the writer of this article that you will be able to understand how iterators are used to simplify the APM. It is important to note here that iterators are mainly used on collections. And, all generic collection classes implement a core set of basic interfaces that enable operations independent of the algorithm used by an implementation. At the top of this interface hierarchy are the IEnumerable<t> and IEnumerator<t> interfaces. The IEnumerator<t> interface implements the MoveNext method, which, we will soon see, is compiler generated during an iterator operation.

The code below shows that the differences include that it returns an IAsyncResult instead of the number of bytes read and that two parameters are added to the method signature to support APM. Those two parameters involve the usage of a callback-style involved in using the APM. In fact, this is one of the publicly stated reasons why programmers avoid using the APM. This is why experts like Jeffrey Richter have developed several ways (and classes) to facilitate using the APM, which brings us to the point of this article. Asynchronous programming basically allows some portions of code to be executed on separate threads. Throughout the .NET Framework, many classes support the APM by supplying BeginXXX and EndXXX versions of methods. For example, the FileStream class has a Read method that reads from a stream. To support the APM model, it also supports BeginRead and EndRead methods. This pattern of using BeingXXX and EndXXX methods allows you to execute methods asynchronously.

using System;
using System.IO;
using System.Threading;
public class Program {
    public static void Main() {
    byte[]  buffer = new byte[100];
    string s = string.Concat(Environment.SystemDirectory, "\\kernel32.dll");
    FileStream fs = new FileStream(s, FileMode.Open, FileAccess.Read, 
                        FileShare.Read, 1024,FileOptions.Asynchronous);

    //make the asynchronous call
    IAsyncResult r = fs.BeginRead(buffer, 0, buffer.Length, null, null);
    // recall that a buffer is an array, thus the Length property
    // now calling EndRead will block until the Async work is complete
    Int32 someBytes = fs.EndRead(r);
    //close the stream
    fs.Close();
    Console.WriteLine("Read {0} Bytes", someBytes);
    Console.WriteLine(BitConverter.ToString(buffer));
  }
}

The output:

Read 100 Bytes
4D-5A-90-00-03-00-00-00-04-00-00-00-FF-FF-00-00-B8-00-00-00-00-00-00-00-40-00-00
-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-0
0-00-00-00-00-00-00-F0-00-00-00-0E-1F-BA-0E-00-B4-09-CD-21-B8-01-4C-CD-21-54-68-
69-73-20-70-72-6F-67-72-61-6D-20-63-61-6E-6E-6F-74-20-62-65

To understand how this works, examine the FileStream.Read method signature:

int Read(byte[] array, int offset, int count)

The BeginRead looks much like the Read method:

IAsyncResult BeginRead(byte[] array, if offset, int someBytes, 
             AsyncCallback, userCallback, object, stateObject

C# Iterators (Language Feature)

The idea of an enumerator is to advance through and read another collection’s contents. C# provides a means to create an iterator: create a method that either returns IEnumerable<t> (or just IEnumerable, the weakly typed equivalent), or IEnumerator<t>, and generates a sequence of values using the yield statement. The magic happens at the compiler level, which means no new IL instructions are generated. The C# compiler will go ahead and create the underlying IEnumerator<t> type for you.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

class DoublerContainer : IEnumerable<int>
{
    private List<int> myList = new List<int>();

    public DoublerContainer(List<int> myList)
    {
      this.myList = myList;
    }

    public IEnumerator<int> GetEnumerator()
    {
       foreach (int i in myList)
           yield return i * 2;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
       return ((IEnumerable<int>)this).GetEnumerator();
    }
        
    static IEnumerable<int> Fibonacci(int max)
    {
        int i1 = 1;
        int i2 = 1;
    
        while (i1 <= max)
        {
            yield return i1;
            int t = i1;
            i1 = i2;
            i2 = t + i2;
        }
    }

    public sealed class Program {
        public static void Main()
        {
            // First, walk a doubler-container:
            Console.WriteLine("Doubler:");
            DoublerContainer dc = new DoublerContainer(
                new List<int>(new int[] { 10, 20, 30, 40, 50 }));
            foreach (int x in dc)
                Console.WriteLine(x);
    
            // Next, walk the fibonacci generator:
            Console.WriteLine("Fibonacci:");
            foreach (int i in Fibonacci(21))
                Console.WriteLine(i);
        }
    }
}

Output

Doubler:
20
40
60
80
100
Fibonacci:
1
1
2
3
5
8
13
21

Rather than manually creating a new IEnumerator<t> class, the C# compiler does it for you. The result is an enumerator that calculates the next value each time a call to MoveNext is made, passing the next value and waiting to be called again via a call to yield. When the method returns, it indicates to the caller that they have reached the end of the collection. Here is a simpler example:

using System;
using System.Collections.Generic;
public class Program {
    private static IEnumerable<int32> CountTo(Int32 max) {
        yield return 1;
        for (Int32 n = 2; n <= max; n++)
        yield return n;
    }

    public static void Main() {
        foreach (Int32 num in CountTo(10))
        {
            Console.Write(num   + " ");
        }
    }
}

Output

1 2 3 4 5 6 7 8 9 10

C# iterators allow you to write a single method that returns an ordered sequence of values. The method has to return either an IEnumerable or an IEnumerator. The code you have in the method is rewritten by the compiler, turning the code into a class, which is effectively a state machine. In the code above, the compiler turns the method into the IEnumerator class, and the class object is a state machine that can be suspended or resumed. The CountTo method returns a collection of integers, counting from 1 up to a maximum value. The class generated by the CountTo method implements the IEnumerator<int32>, IEnumerator, and IDisposable interfaces. It is a compiler-generated class so it has the compiler generated attribute on it. All of the local variables that you have for that method turn into fields for the class. The C# compiler also adds a field called private Int32 m_state = 0 to indicate where it is in the state machine processing. That is always initialized to zero. Then, there is this private Int32 m_current. But then, the compiler generates the MoveNext method. This means that whenever you use a foreach statement when using iterators in C# 2.0, the machine calls MoveNext internally. When it foreach's around after processing the initial value of 1, it will call MoveNext again.

[CompilerGenerated]
private sealed class <countto>d_0:
    IEnumerator<Int32>, IEnumerator, IDisposable 
    private Int32 m_state = 0; // state machine location
    private Int32 m_current;     // last yield return value
    public Int32 max;        //arguments become fields
    private Int32 n;         // locals become fields

    Int32 Current {  get {  return m_current; } }
     . . . 
}

public Boolean MoveNext() {
    switch (m_state) {
        case 0: 
            m_current = 1; m_state = 1; return true;
        case 1:
            n = 2;
            while ( n <= max)
            {
                m_current = n; m_state = 2;  return true;
                Label_ContinueLoop; n++;
            }
            break;
        case 2: 
            goto Label_ContinueLoop;
    }
    return false;
}

You see, the foreach statement gives you an easy way to iterate over all the items in a collection class. However, there are lots of different kinds of collection classes—arrays, lists, dictionaries, trees, linked-lists, and so on—and each uses its own internal data structure for representing the collection of items. When you use the foreach statement, you're saying that you want to iterate over all the items in the collection and you don't care what data structure the collection class uses internally to maintain its items. The foreach statement causes the compiler to generate a call to the collection class' GetEnumerator method. This method creates an object that implements the IEnumerator<t> interface. This object knows how to traverse the collection's internal data structure. Each iteration of the while loop calls the IEnumerator<t> object's MoveNext method. This method tells the object to advance to the next item in the collection and return true if successful or false if all items have been enumerated. Internally, MoveNext might just increment an index, or it might advance to the next node of a linked-list, or it might traverse up or down a tree. The whole point of the MoveNext method is to abstract away the collection's internal data structures from the code that is executing the foreach loop.

If MoveNext returns true, then the call to the Current property's get accessor method returns the value of the item that the MoveNext method just advanced to so that the body of the foreach statement (the code inside the try block) can process the item. When MoveNext determines that all of the collection's items have been processed, it returns false. At this point, the while loop exits and the finally block is entered. In the finally block, a check is made to determine whether the IEnumerator<t> object implements the IDisposable interface and, if so, its Dispose method is called. For the record, the IEnumerator<t> interface derives from IDisposable, thereby requiring all IEnumerator<t> objects to implement the Dispose method. Some enumerator objects require additional resources while iterating. Perhaps the object returns lines of text from a text file. When the foreach loop exits (which could happen before iterating though all the items in the collection), the finally block calls Dispose, allowing the IEnumerator<t> object to release these additional resources—closing the text file, for example.

Splitting APM code into Callback Methods

A method callback technique works by first queuing up an asynchronous I/O request. Then, your thread continues doing whatever it wants to do. When the I/O request completes, Windows queues a work item into the CLR’s thread pool. Eventually, a thread pool thread will dequeue the work item and calls some method you have written; this is how you know that the asynchronous I/O operation has completed. That is, the callback technique requires that we specify a method to callback on and include any state that we need in the callback method to complete the call:

using System;
using System.IO;
using System.Threading;

public static class Program {
   // The array is static so it can be accessed by Main and ReadIsDone
   private static Byte[] s_data = new Byte[100];

   public static void Main() {
      ReadMultipleFiles(@"C:\windows\system32\config.NT", @"C:\point.cs");
      APMCallbackUsingAnonymousMethod();
      // Show the ID of the thread executing Main
      Console.WriteLine("Main thread ID={0}", 
         Thread.CurrentThread.ManagedThreadId);

      //ReadMultipleFiles(@"C:\windows\system32\config.NT", @"c:\Point.cs");
      // Open the file indicating asynchronous I/O
      FileStream fs = new FileStream(@"C:\windows\system32\config.NT", FileMode.Open,
         FileAccess.Read, FileShare.Read, 1024,
         FileOptions.Asynchronous);

      // Initiate an asynchronous read operation against the FileStream
      // Pass the FileStream (fs) to the callback method (ReadIsDone)
      fs.BeginRead(s_data, 0, s_data.Length, ReadIsDone, fs);

      // Executing some other code here would be useful...

      // For this demo, I'll just suspend the primary thread
      Console.ReadLine();
   }

   private static void ReadIsDone(IAsyncResult ar) {
      // Show the ID of the thread executing ReadIsDone
      Console.WriteLine("ReadIsDone thread ID={0}",
         Thread.CurrentThread.ManagedThreadId);

      // Extract the FileStream (state) out of the IAsyncResult object
      FileStream fs = (FileStream) ar.AsyncState;

      // Get the result
      Int32 bytesRead = fs.EndRead(ar);

      // No other operations to do, close the file
      fs.Close();

      // Now, it is OK to access the byte array and show the result.
      Console.WriteLine("Number of bytes read={0}", bytesRead);
      Console.WriteLine(BitConverter.ToString(s_data, 0, bytesRead));
   }

   private static void APMCallbackUsingAnonymousMethod() {
      // Show the ID of the thread executing Main
      Console.WriteLine("Main thread ID={0}",
         Thread.CurrentThread.ManagedThreadId);

      // Open the file indicating asynchronous I/O
      FileStream fs = new FileStream(@"C:\windows\system32\config.NT", FileMode.Open,
         FileAccess.Read, FileShare.Read, 1024,
         FileOptions.Asynchronous);

      Byte[] data = new Byte[100];

      // Initiate an asynchronous read operation against the FileStream
      // Pass the FileStream (fs) to the callback method (ReadIsDone)
      fs.BeginRead(data, 0, data.Length,
         delegate(IAsyncResult ar)
         {
            // Show the ID of the thread executing ReadIsDone
            Console.WriteLine("ReadIsDone thread ID={0}",
               Thread.CurrentThread.ManagedThreadId);

            // Get the result
            Int32 bytesRead = fs.EndRead(ar);

            // No other operations to do, close the file
            fs.Close();

            // Now, it is OK to access the byte array and show the result.
            Console.WriteLine("Number of bytes read={0}", bytesRead);
            Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));

         }, null);

      // Executing some other code here would be useful...

      // For this demo, I'll just suspend the primary thread
      Console.ReadLine();
   }

   private static void ReadMultipleFiles(params String[] pathnames) {
      for (Int32 n = 0; n < pathnames.Length; n++) {
         // Open the file indicating asynchronous I/O
         Stream stream = new FileStream(pathnames[n], FileMode.Open,
            FileAccess.Read, FileShare.Read, 1024,
            FileOptions.Asynchronous);

         // Initiate an asynchronous read operation against the Stream
         new AsyncStreamRead(stream, 100,
            delegate(Byte[] data)
            {
               // Process the data.
               Console.WriteLine("Number of bytes read={0}", data.Length);
               Console.WriteLine(BitConverter.ToString(data));
            });
      }

      // All streams have been opened and all read requests have been 
      // queued; they are all executing concurrently and they will be 
      // processed as they complete!

      // The primary thread could do other stuff here if it wants to...

      // For this demo, I'll just suspend the primary thread
      Console.ReadLine();
   }

The code executes to output:

Number of bytes read=100
Number of bytes read=100
75-73-69-6E-67-20-53-79-73-74-65-6D-3B-0D-0A-0D-0A-70-75-62-6C-69-63-20-73-74-61
-74-69-63-20-63-6C-61-73-73-20-50-72-6F-67-72-61-6D-20-7B-0D-0A-20-20-20-70-75-6
2-6C-69-63-20-73-74-61-74-69-63-20-76-6F-69-64-20-4D-61-69-6E-28-73-74-72-69-6E-
67-5B-5D-20-61-72-67-73-29-20-7B-0D-0A-20-20-20-20-20-20-56
52-45-4D-20-57-69-6E-64-6F-77-73-20-4D-53-2D-44-4F-53-20-53-74-61-72-74-75-70-20
-46-69-6C-65-0D-0A-52-45-4D-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-53-20-7
6-73-20-43-4F-4E-46-49-47-2E-4E-54-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-
53-20-69-73-20-6E-6F-74-20-75-73-65-64-20-74-6F-20-69-6E-69

Main thread ID=1
ReadIsDone thread ID=4
Number of bytes read=100
52-45-4D-20-57-69-6E-64-6F-77-73-20-4D-53-2D-44-4F-53-20-53-74-61-72-74-75-70-20
-46-69-6C-65-0D-0A-52-45-4D-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-53-20-7
6-73-20-43-4F-4E-46-49-47-2E-4E-54-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-
53-20-69-73-20-6E-6F-74-20-75-73-65-64-20-74-6F-20-69-6E-69

Main thread ID=1
ReadIsDone thread ID=4
Number of bytes read=100
52-45-4D-20-57-69-6E-64-6F-77-73-20-4D-53-2D-44-4F-53-20-53-74-61-72-74-75-70-20
-46-69-6C-65-0D-0A-52-45-4D-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-53-20-7
6-73-20-43-4F-4E-46-49-47-2E-4E-54-0D-0A-52-45-4D-20-43-4F-4E-46-49-47-2E-53-59-
53-20-69-73-20-6E-6F-74-20-75-73-65-64-20-74-6F-20-69-6E-69

A Review Before Some Examples Using the Power Threading Library

The APM’s pattern is to enable highly-scalable I/O and to permit worker threads to make forward progress in parallel with a stream operation. Any type of thread-synchronous operation would wait until the I/O operation completes. This blocks threads, and while there may an idle CPU, the resources used by those threads are wasting, as they have been made available to those threads. Access to those resources must occur in an orderly fashion. When you examine the System.IO namespace, you will notice that the APM’s BeginRead and BeginWrite methods take similar arguments to the Read and Write methods used in-file operations. Each has corresponding EndRead and EndWrite methods:

public virtual IAsyncResult BeginRead(byte[]  buffer, int offset, 
               int count, AsyncCallback callback, object state);
public virtual int EndRead(IAsyncResult asyncResult);
public virtual IAsyncResult BeginWrite(byte[]  buffer, int offset, 
               int count, AsyncCallback callback, object state);
public virtual void EndWrite(IAsyncResult asyncResult);

File I/O normally deals bidirectional data in a pipe, called a stream. An operation on a stream called in this manner usually occurs on another thread, normally from the thread pool. But, notice that each method takes an additional AsyncCallback and an object parameter. Further, each returns an AsyncResult. These are used to rendezvous with the operation in one of three ways:

  • The AsyncCallback delegate can be supplied that will be called when the I/O request has been completed. This delegate is given access to the results of the operation for processing.
  • The IsCompleted property on the IAsyncResult can be pooled to determine whether an I/O request has been completed.
  • You can block the worker thread and wait for completion using the AsyncWaitHandle on the IAsyncResult.

Here is code meant to provide a conceptual view:

using System;
using System.Threading;

class App {
   static void Main() {
      Console.WriteLine("Main thread: Queuing an aynchronous operation.");
      ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation));

      Console.WriteLine("Main thread: Performing other operations.");
      // ...

      Console.WriteLine("Main thread: Pausing to simulate doing other operations.");
      Console.ReadLine();
   }

   // The callback method's signature MUST match that of a 
   // System.Threading.WaitCallback delegate (it takes an 
   // Object parameter and returns void)
   static void MyAsyncOperation(Object state) {
      Console.WriteLine("ThreadPool thread: Performing aynchronous operation.");
      // ...
      Thread.Sleep(5000);    // Sleep for 5 seconds to simulate doing 
                             // work

      // Returning from this method causes the thread to 
      // suspend itself waiting for another task
   }
}

Compiling this code results the following output:

Main thread: Queuing an aynchronous operation.
Main thread: Performing other operations.
Main thread: Pausing to simulate doing other operations.
ThreadPool thread: Performing aynchronous operation.

Using Wintellect’s Power Threading Library

The Power Threading library is free for download at http://wintellect.com. The main threading DLL should be either xcopied or copied and pasted into your .NET directory. The examples shown do not make use of the famous AsyncEnum class, which ties together how powerful iterators are when used to simplify the APM. Rather, the code examples will show APM tests, and reveal logical processor information:

using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Wintellect;
using Wintellect.Threading;
using Wintellect.Threading.AsyncProgModel;
public static class Program {
   public static void Main() {
      // Functional: Test the APM Implementations
      FunctionalTest();

      // Performance: Compare the performance of various APM Implementations
      PerformanceTest();

      // Functional: How to derive from AsyncResult<tresult>
      // and performs an async I/O operation
      AsyncIOWithResult.Test();

      // Functional: How to defive from AsyncResultNoResult
      // and perform an async I/O operation
      AsyncIONoResult.Test();
   }

   private static APMTestClass s_apmTestClass = new APMTestClass();

   #region FunctionalTests
   public static void FunctionalTest() {
      // Functional tests: No callback 
      s_apmTestClass.Compute(false);
      try { s_apmTestClass.Compute(true); }
      catch (InvalidOperationException) { Console.WriteLine("Caught"); }

      s_apmTestClass.EndComputeDelegate(
               s_apmTestClass.BeginComputeDelegate(false, null, null));
      try { s_apmTestClass.EndComputeDelegate(
               s_apmTestClass.BeginComputeDelegate(true, null, null)); }
      catch (InvalidOperationException) { Console.WriteLine("Caught"); }

#if false
      s_apmTestClass.EndComputeReflection(
            s_apmTestClass.BeginComputeReflection(false, null, null));
      try { s_apmTestClass.EndComputeReflection(
            s_apmTestClass.BeginComputeReflection(true, null, null)); }
      catch (InvalidOperationException) { Console.WriteLine("Caught"); }
#endif

      s_apmTestClass.EndComputeSpecific(
            s_apmTestClass.BeginComputeSpecific(false, null, null));
      try { s_apmTestClass.EndComputeSpecific(
            s_apmTestClass.BeginComputeSpecific(true, null, null)); }
      catch (InvalidOperationException) { Console.WriteLine("Caught"); }

      // Functional tests: Callback 
      s_apmTestClass.BeginComputeDelegate(false, delegate(IAsyncResult ar) {
         ((APMTestClass)ar.AsyncState).EndComputeDelegate(ar);
      }, s_apmTestClass);

#if false
      s_apmTestClass.BeginComputeReflection(false, delegate(IAsyncResult ar) {
         ((APMTestClass)ar.AsyncState).EndComputeReflection(ar);
      }, s_apmTestClass);
#endif

      s_apmTestClass.BeginComputeSpecific(false, delegate(IAsyncResult ar) {
         ((APMTestClass)ar.AsyncState).EndComputeSpecific(ar);
      }, s_apmTestClass);

      Thread.Sleep(2000);
      Console.WriteLine("Functional tests complete.");
   }
   #endregion

   #region PerformanceTests
   public static void PerformanceTest() {
      const Int32 count = 100 * 1000;

      // Performance tests: No callback
      using (new OperationTimer("Compute, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.Compute(false);
         }
      }

      using (new OperationTimer("BeginComputeDelegate, no callback, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.EndComputeDelegate(
                 s_apmTestClass.BeginComputeDelegate(false, null, null));
         }
      }

#if false
      using (new OperationTimer("BeginComputeReflection, no callback, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.EndComputeReflection(
               s_apmTestClass.BeginComputeReflection(false, null, null));
         }
      }
#endif

      using (new OperationTimer("BeginComputeSpecific, no callback, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.EndComputeSpecific(
               s_apmTestClass.BeginComputeSpecific(false, null, null));
         }
      }

      // Performance tests: Callback
      AutoResetEvent done = new AutoResetEvent(false);
      Int32 countdown;

      countdown = count;
      AsyncCallback acDelegate = delegate(IAsyncResult ar) {
         ((APMTestClass)ar.AsyncState).EndComputeDelegate(ar);
         if (Interlocked.Decrement(ref countdown) == 0) done.Set();
      };
      using (new OperationTimer("BeginComputeDelegate, callback, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.BeginComputeDelegate(false, acDelegate, s_apmTestClass);
         }
         done.WaitOne();
      }

#if false
      countdown = count;
      AsyncCallback acReflection = delegate(IAsyncResult ar) {
         ((APMTestClass)ar.AsyncState).EndComputeReflection(ar);
         if (Interlocked.Decrement(ref countdown) == 0) done.Set();
      };
      using (new OperationTimer("BeginComputeReflection, callback, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.BeginComputeReflection(false, acReflection, s_apmTestClass);
         }
         done.WaitOne();
      }
#endif


      countdown = count;
      AsyncCallback acSpecific = delegate(IAsyncResult ar) {
         ((APMTestClass)ar.AsyncState).EndComputeSpecific(ar);
         if (Interlocked.Decrement(ref countdown) == 0) done.Set();
      };
      using (new OperationTimer("BeginComputeSpecific, callback, no throw")) {
         for (Int32 x = 0; x < count; x++) {
            s_apmTestClass.BeginComputeSpecific(false, acSpecific, s_apmTestClass);
         }
         done.WaitOne();
      }

      Console.WriteLine("Performance tests complete.");
      Console.ReadLine();
   }
   #endregion
}


///////////////////////////////////////////////////////////////////////////////


internal sealed class APMTestClass {
   private delegate String ComputeDelegate(Boolean fail);
   private static ComputeDelegate s_Compute;
#if false
   private static MethodInfo s_MethodInfo;
#endif

   public String Compute(Boolean fail) {
      if (fail) throw new InvalidOperationException();
      return null;
   }

   public IAsyncResult BeginComputeDelegate(Boolean fail, AsyncCallback ac, Object state) {
      if (s_Compute == null) {
         Interlocked.CompareExchange(ref s_Compute, new ComputeDelegate(Compute), null);
      }
      return s_Compute.BeginInvoke(fail, ac, state);
   }
public String EndComputeDelegate(IAsyncResult ar) {
      return s_Compute.EndInvoke(ar);
   }

#if false
   public IAsyncResult BeginComputeReflection(Boolean fail, 
                       AsyncCallback ac, Object state) {
      if (s_MethodInfo == null) {
         Interlocked.CompareExchange(ref s_MethodInfo, 
             typeof(APMTestClass).GetMethod("Compute"), null);
      }
      return new AsyncResultReflection<string>(ac, state, this, s_MethodInfo, fail);
   }
   public String EndComputeReflection(IAsyncResult ar) {
      return ((AsyncResultReflection<string>)ar).EndInvoke();
   }
#endif

   private class ComputeAsyncResult : AsyncResult<string> {
      // State needed to do the asynchronous operation
      private readonly APMTestClass m_target;
      private readonly Boolean m_fail;

      public ComputeAsyncResult(AsyncCallback ac, Object state, 
                                APMTestClass target, Boolean fail)
         : base(ac, state) {
         m_target = target;
         m_fail = fail;
         BeginInvokeOnWorkerThread();
      }
      protected override String OnCompleteOperation(IAsyncResult ar) {
         return m_target.Compute(m_fail);
      }
   }

   public IAsyncResult BeginComputeSpecific(Boolean fail, 
                       AsyncCallback ac, Object state) {
      return new ComputeAsyncResult(ac, state, this, fail);
   }

   public String EndComputeSpecific(IAsyncResult ar) {
      return ((ComputeAsyncResult)ar).EndInvoke();
   }
}

// This shows how to derive from AsyncResult<tresult> and create a method that
// internally performs an async I/O operation (not a compute operation)
internal sealed class AsyncIOWithResult : AsyncResult<string> {
   private static AsyncIOWithResult s_object;

   public static void Test() {
      AsyncIOWithResult o = new AsyncIOWithResult(delegate(IAsyncResult ar) {
         Console.WriteLine(s_object.EndInvoke());
      }, null);

      Console.WriteLine("Enter <enter> when you see the result");
      Console.ReadLine();
   }

   private FileStream m_fs;
   private Byte[] m_bytes = new Byte[10000];

   public AsyncIOWithResult(AsyncCallback ac, Object state)
      : base(ac, state) {
      s_object = this;
      m_fs = new System.IO.FileStream(@"c:\windows\system32\Config.NT", FileMode.Open);
      m_fs.BeginRead(m_bytes, 0, m_bytes.Length, GetAsyncCallbackHelper(), this);
   }

   protected sealed override String OnCompleteOperation(IAsyncResult ar) {
      Int32 x = m_fs.EndRead(ar);
      m_fs.Close();
      Array.Resize(ref m_bytes, x);
      return Encoding.ASCII.GetString(m_bytes);
   }
}

internal sealed class AsyncIONoResult : AsyncResult {
   private static AsyncIONoResult s_object;

   public static void Test() {
      AsyncIONoResult o = new AsyncIONoResult(delegate(IAsyncResult ar) {
         s_object.EndInvoke();
         Console.WriteLine("Done reading");
      }, null);

      Console.WriteLine("Enter <enter> when you see the result");
      Console.ReadLine();
   }

   private FileStream m_fs;
   private Byte[] m_bytes = new Byte[10000];

   public AsyncIONoResult(AsyncCallback ac, Object state)
      : base(ac, state) {
      s_object = this;
      m_fs = new System.IO.FileStream(@"c:\windows\system32\config.NT", FileMode.Open);
      m_fs.BeginRead(m_bytes, 0, m_bytes.Length, GetAsyncCallbackHelper(), this);
   }

   protected sealed override void OnCompleteOperation(IAsyncResult ar) {
      Int32 x = m_fs.EndRead(ar);
      m_fs.Close();
      Array.Resize(ref m_bytes, x);
      String s = Encoding.ASCII.GetString(m_bytes);
      Console.WriteLine(s);
   }
}

We compile this code using /reference:Wintellect.Threading.dll and obtain the following results:

Caught
Caught
Caught
Functional tests complete.
      2 (GCs=  0) Compute, no throw
  5,825 (GCs=104) BeginComputeDelegate, no callback, no throw
  2,015 (GCs= 17) BeginComputeSpecific, no callback, no throw
  2,080 (GCs= 42) BeginComputeDelegate, callback, no throw
    107 (GCs=  3) BeginComputeSpecific, callback, no throw
Performance tests complete.
Enter <enter> when you see the result

REM Windows MS-DOS Startup File
REM
REM CONFIG.SYS vs CONFIG.NT
REM CONFIG.SYS is not used to initialize the MS-DOS environment.
REM CONFIG.NT is used to initialize the MS-DOS environment unless a
REM different startup file is specified in an application's PIF.
REM
. . . . .  and so on

REM You can use EMM command line to configure EMM(Expanded Memory Manager).
REM The syntax is:
REM
REM
dos=high, umb
device=%SystemRoot%\system32\himem.sys
files=40


Enter <enter> when you see the result

REM Windows MS-DOS Startup File
REM
REM CONFIG.SYS vs CONFIG.NT
REM CONFIG.SYS is not used to initialize the MS-DOS environment.
REM CONFIG.NT is used to initialize the MS-DOS environment unless a
REM different startup file is specified in an application's PIF.
REM
. . . . and so on
REM     The EMM size is determined by pif file(either the one associated
REM     with your application or _default.pif). If the size from PIF file
REM     is zero, EMM will be disabled and the EMM line will be ignored.
REM
dos=high, umb
device=%SystemRoot%\system32\himem.sys
files=40
done reading

Here is code that uses APM testing to fetch information about your logical processor:

using System;
using Wintellect.Threading.LogicalProcessor;

public static class Program {
   static void Main(string[] args) {
      if (Environment.OSVersion.Version < new Version(6, 0, 0, 0)) {
         Console.WriteLine("This sample requires Windows Vista or later.");
         return;
      }

      Console.WriteLine("Logical Processor Information");
      LogicalProcessorInformation[] lpis = 
         LogicalProcessorInformation.GetLogicalProcessorInformation();
      Array.ForEach(lpis, Console.WriteLine);
   }
}

This code is compiled with a reference to the Wintellect.Threading DLL to obtain the following results:

Logical Processor Information
Mask=1, ProcessorCore=0
Mask=1, Cache=Level=L1, Associativity=8, LineSize=64, Size=32,768, Type=Data
Mask=1, Cache=Level=L1, Associativity=8, LineSize=64, Size=32,768, Type=Instruction
Mask=3, Package
Mask=2, ProcessorCore=0
Mask=2, Cache=Level=L1, Associativity=8, LineSize=64, Size=32,768, Type=Data
Mask=2, Cache=Level=L1, Associativity=8, LineSize=64, Size=32,768, Type=Instruction
Mask=3, Cache=Level=L2, Associativity=8, LineSize=64, Size=2,097,152, Type=Unified
Mask=3, NumaNode=0

And finally, here is code that tests resource locks:

/******************************************************************************
Module:  ResourceLockTests.cs
Notices: Copyright (c) 2006-2008 by Jeffrey Richter and Wintellect
******************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Wintellect;
using Wintellect.Threading;
using Wintellect.Threading.ResourceLocks;
using Wintellect.Threading.ResourceLocks.Diagnostics;

public static class Program {
   public static void Main() {
      UInt32 x = 5;
      x = InterlockedEx.CompareExchange(ref x, 7, 2);
      UInt32 y = InterlockedEx.CompareExchange(ref x, 7, 5);

      String[] notice = new String[] {
         "This tester app contains code which performs functional and performance",
         "tests against the various classes in Wintellect's Power Threading Library.",
         null,
         "The code in here can give you an idea of how to use some of these classes",
         "but it is not the purpose of this code.",
         null,
         "Also, please note that some of the classes in the Power Threading Library",
         "require Windows Vista. Attempting to use these classes on a pre-Vista OS",
         "will result in an exception such as EntryPointNotFoundException.",
         null
      };
      Array.ForEach(notice, Console.WriteLine);

      Process.GetCurrentProcess().PriorityBoostEnabled = false;

      // Performance: Compare performance of various locks
      PerfTest_ResourceLocks();

      // Functional: Test a specific ResourceLock-derived class
      //FuncTest_ResourceLock(new OptexResourceLock());

      // Functional: Test a specific ResourceLock-derived class
      //StressTest_ResourceLocks();

      // Functional: Test the Deadlock detector
      //FuncTest_DeadlockDetector();
   }

   private static void FuncTest_ResourceLock(ResourceLock rl) {
      FuncTest_ResourceLock rlt = new FuncTest_ResourceLock(8);
      rlt.Test(rl);
      //rlt.Test(new OneManyResourceLock());
      //rlt.Test(new OneManySpinResourceLock());
   }

   private static void StressTest_ResourceLocks() {
      StressTest_ResourceLock strl = 
              new StressTest_ResourceLock(20, 1000 * 1000, 5, 2000);
      foreach (Type t in typeof(ResourceLock).Assembly.GetExportedTypes()) {
         // Consider only ResourceLock-derived types
         if (!typeof(ResourceLock).IsAssignableFrom(t)) continue;
         // Skip over diagnostic locks
         if (typeof(ResourceLockObserver).IsAssignableFrom(t)) continue;
         if (t == typeof(ResourceLock)) continue;
         // Skip the base class

         Console.WriteLine("Stress testing: {0}", t.FullName);
         ResourceLock rl = (ResourceLock) Activator.CreateInstance(t);
         strl.Test(rl);
         Console.WriteLine();
      }
   }

   private static void PerfTest_ResourceLocks() {
      Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Idle;
      const Int32 c_PerfIterations = 2 * 1000 * 1000;
      PerfTest_ResourceLocks cpt = new PerfTest_ResourceLocks(c_PerfIterations);
      cpt.CalcResults();
      Console.WriteLine("\nSorted Results:");
      cpt.ShowResultsByThreads();
      cpt.ShowResultsByType('\t');
      // Use ',' for CSV file which can be loaded into Excel
   }


   #region Deadlock Detector Funcional Tests
   private static MonitorResourceLock s_lockA = new MonitorResourceLock();
   private static MonitorResourceLock s_lockB = new MonitorResourceLock();
   private static MonitorResourceLock s_lockC = new MonitorResourceLock();

   private static void FuncTest_Deadlock_Test(ResourceLock l, 
           Boolean write1st, Boolean write2nd, Boolean expectedToFail) {
      try {
         try {
            using (write1st ? l.WaitToWrite() : l.WaitToRead()) {
               using (write2nd ? l.WaitToWrite() : l.WaitToRead()) { }
            }
            if (expectedToFail)
             throw new InvalidProgramException("No deadlock detected when expected.");
         }
         catch (Exception<deadlockexceptionargs>) {
            if (!expectedToFail)
             throw new InvalidProgramException("Deadlock detected when not expected.");
         }
      }
      catch (InvalidOperationException) {
         // This can happen if deadlock detector throws before taking
         // a lock and the we try to release the lock anyway
      }
      //DeadlockDetector.ForceClear();
   }


   private static void FuncTest_DeadlockDetector() {
      ResourceLock.PerformDeadlockDetection(true);    // Turn on deadlock detection

      // Exclusive lock: Owned, Recursive
      // AW/AW: no deadlock
      FuncTest_Deadlock_Test(new MonitorResourceLock(), true, true, false);
      // AR/AR: no deadlock
      FuncTest_Deadlock_Test(new MonitorResourceLock(), false, false, false);
      // AW/AR: no deadlock
      FuncTest_Deadlock_Test(new MonitorResourceLock(), true, false, false);
      // AR/AW: no deadlock
      FuncTest_Deadlock_Test(new MonitorResourceLock(), false, true, false);

      // Exclusive lock: Unowned, Non-recursive
      // AW/AW: deadlock
      FuncTest_Deadlock_Test(new OptexResourceLock(), true, true, true);
      // AR/AR: deadlock
      FuncTest_Deadlock_Test(new OptexResourceLock(), false, false, true);
      // AW/AR: deadlock
      FuncTest_Deadlock_Test(new OptexResourceLock(), true, false, true);
      // AR/AW: deadlock
      FuncTest_Deadlock_Test(new OptexResourceLock(), false, true, true);

      // Exclusive lock: Owned, Non-recursive
      // AW/AW: deadlock
      FuncTest_Deadlock_Test(new ExclusiveOwnerResourceLockModifier(
                             new OptexResourceLock()), true, true, true);
      // AR/AR: deadlock
      FuncTest_Deadlock_Test(new ExclusiveOwnerResourceLockModifier(
                             new OptexResourceLock()), false, false, true);
      // AW/AR: deadlock
      FuncTest_Deadlock_Test(new ExclusiveOwnerResourceLockModifier(
                             new OptexResourceLock()), true, false, true);
      // AR/AW: deadlock
      FuncTest_Deadlock_Test(new ExclusiveOwnerResourceLockModifier(
                             new OptexResourceLock()), false, true, true);

      // Reader/Writer lock: Owned, Recursive
      // AW/AW: no deadlock
      FuncTest_Deadlock_Test(new ReaderWriterResourceLock(), true, true, false);
      // AR/AR: no deadlock
      FuncTest_Deadlock_Test(new ReaderWriterResourceLock(), false, false, false);
      // AW/AR: deadlock
      FuncTest_Deadlock_Test(new ReaderWriterResourceLock(), true, false, true);
      // AR/AW: deadlock
      FuncTest_Deadlock_Test(new ReaderWriterResourceLock(), false, true, true);

      // Reader/Writer lock: Unowned, Non-recursive
      // AW/AW: deadlock
      FuncTest_Deadlock_Test(new OneManyResourceLock(), true, true, true);
      // AR/AR: no deadlock
      FuncTest_Deadlock_Test(new OneManyResourceLock(), false, false, false);
      // AW/AR: deadlock
      FuncTest_Deadlock_Test(new OneManyResourceLock(), true, false, true);
      // AR/AW: deadlock
      FuncTest_Deadlock_Test(new OneManyResourceLock(), false, true, true);

      // Reader/Writer lock: Owned, Non-recursive (not a valid combination)


      s_lockA.Name = "Lock A";
      s_lockB.Name = "Lock B";
      s_lockC.Name = "Lock C";

      using (s_lockA.WaitToWrite()) {
         Thread t = new Thread(delegate(Object o) {
            Thread.Sleep(2000);
            s_lockA.WaitToWrite();
         });
         t.Start();
         //DeadlockDetector.BlockForJoin(t, Timeout.Infinite);
      }
      FuncTest_DeadlockDetector_LockA(null);
   }

   private static void FuncTest_DeadlockDetector_LockA(Object o) {
      using (s_lockA.WaitToRead()) {
         ThreadPool.QueueUserWorkItem(FuncTest_DeadlockDetector_LockB);
         MessageBox.Show("Let thread 1 get B");
         using (s_lockB.WaitToRead()) {
         }
      }
   }

   private static void FuncTest_DeadlockDetector_LockB(Object o) {
      using (s_lockB.WaitToRead()) {
         AppDomain ad = AppDomain.CreateDomain("Other AD", null, null);
         ThreadPool.QueueUserWorkItem(
           delegate { ad.DoCallBack(FuncTest_DeadlockDetector_LockC); });
         MessageBox.Show("Let thread 2 get C");
         using (s_lockC.WaitToRead()) {
         }
      }
   }

   private static void FuncTest_DeadlockDetector_LockC() {
      using (s_lockC.WaitToRead()) {
         MessageBox.Show("Let thread 3 get A");
         using (s_lockA.WaitToRead()) { }
      }
   }
   #endregion
}


///////////////////////////////////////////////////////////////////////////////


internal sealed class FuncTest_ResourceLock {
   private ManualResetEvent[] m_Threads;
   private ResourceLock m_ResourceLock;

   public FuncTest_ResourceLock(Int32 numThreads) {
      m_Threads = new ManualResetEvent[numThreads];
   }

   public void Test(ResourceLock rl) {
      m_ResourceLock = new ThreadSafeCheckerResourceLockObserver(rl);

      // Spawn a bunch of threads that will attempt to read/write
      for (Int32 ThreadNum = 0; ThreadNum < m_Threads.Length; ThreadNum++) {
         m_Threads[ThreadNum] = new ManualResetEvent(false);
         ThreadPool.QueueUserWorkItem(ThreadFunc, ThreadNum);
      }
      WaitHandle.WaitAll(m_Threads);
      Console.WriteLine(m_ResourceLock.ToString());
   }

   private void ThreadFunc(Object state) {
      String caption = 
        String.Format("ResourceLock FuncTest_Deadlock_Test: Thread {0}", state);
      DialogResult dr = MessageBox.Show("YES: Attempt to read\nNO: Attempt to write",
          caption, MessageBoxButtons.YesNo);
      // Attempt to read or write
      using ((dr == DialogResult.Yes) ? m_ResourceLock.WaitToRead() : 
                                        m_ResourceLock.WaitToWrite()) {
         MessageBox.Show(m_ResourceLock.ToString() + Environment.NewLine +
           ((dr == DialogResult.Yes) ? "OK stops READING" : "OK stops WRITING"),
           caption, MessageBoxButtons.OK);
      }
      m_Threads[(Int32) state].Set();
   }
}


///////////////////////////////////////////////////////////////////////////////


internal sealed class PerfTest_ResourceLock {
   private Int32 m_iterations;
   public PerfTest_ResourceLock(Int32 iterations) {
      m_iterations = iterations;
   }

   public TimeSpan Test(Boolean write, Int32 threadCount, ResourceLock rl) {
      // Make sure that the methods are JITted
      // so that JIT time is not included in the results
      using (rl.WaitToRead()) { }
      using (rl.WaitToWrite()) { }
      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();

      try {
         Thread.CurrentThread.Priority = ThreadPriority.Highest;
         Stopwatch stopWatch = Stopwatch.StartNew();
         Thread[] threads = new Thread[threadCount - 1];
         for (Int32 t = 0; t < threads.Length - 1; t++) {
            threads[t] = new Thread((ThreadStart) delegate { Loop(write, rl); });
            threads[t].Name = "FuncTest_Deadlock_Test thread #" + t;
            threads[t].Start();
         }
         Loop(write, rl);
         for (Int32 t = 0; t < threads.Length - 1; t++) {
            threads[t].Join();
            //threads[ls].Dispose();
         }
         return stopWatch.Elapsed;
      }
      finally {
         Thread.CurrentThread.Priority = ThreadPriority.Normal;
      }
   }

   private void Loop(Boolean write, ResourceLock rl) {
      Int32 z = 0;
      for (Int32 x = 0; x < m_iterations; x++) {
         if (write) using (rl.WaitToWrite()) { z = x; }
           else using (rl.WaitToRead()) { Int32 y = z; }
      }
   }
}


///////////////////////////////////////////////////////////////////////////////


internal sealed class StressTest_ResourceLock {
   private readonly Int32 m_threadCount;
   private readonly Int32 m_iterations;
   private readonly Int32 m_readerWriterRatio;
   private readonly Int32 m_workSpinCount;

   public StressTest_ResourceLock(Int32 threadCount, Int32 iterations, 
                                  Int32 readerWriterRatio, Int32 workSpinCount) {
      m_threadCount = threadCount;
      m_iterations = iterations;
      m_readerWriterRatio = readerWriterRatio;
      m_workSpinCount = workSpinCount;
   }

   public void Test(ResourceLock rl) {
      // Use the thread-safety checker and the statistics
      // gatherer in case anything goes wrong
      rl = new StatisticsGatheringResourceLockObserver(
                         new ThreadSafeCheckerResourceLockObserver(rl));

      Thread[] threads = new Thread[m_threadCount - 1];
      for (Int32 t = 0; t < threads.Length - 1; t++) {
         threads[t] = new Thread((ThreadStart) delegate { StressLoop(rl); });
         threads[t].Name = "FuncTest_Deadlock_Test thread #" + t;
         threads[t].Start();
      }
      StressLoop(rl); // This thread will do it too
      for (Int32 t = 0; t < threads.Length - 1; t++)
         threads[t].Join();
   }

   private void StressLoop(ResourceLock rl) {
      Random random = new Random((Int32) DateTime.Now.Ticks);
      for (Int32 i = 0; i < m_iterations; i++) {
         if ((i > 0) && (i % (m_iterations / 10)) == 0)
            Console.WriteLine("   {0}: iteration={1}", 
                              Thread.CurrentThread.Name, i);

         if (random.Next(m_readerWriterRatio) == 0) {
            using (rl.WaitToWrite()) {
               for (Int32 work = 0; work < m_workSpinCount; work++) ;
            }
         } else {
            using (rl.WaitToRead()) {
               for (Int32 work = 0; work < m_workSpinCount; work++) ;
            }
         }
      }
   }
}

///////////////////////////////////////////////////////////////////////////////


internal sealed class PerfTest_ResourceLocks {
   private IList<resourcelock /> m_Locks = new List<resourcelock />();
   private Int32[] m_NumThreads = new Int32[] { 1, 2 };
   private TimeSpan[, ,] m_ResultTimes;
   private Int32 m_iterations;
   private PerfTest_ResourceLock m_pt;

   public PerfTest_ResourceLocks(Int32 iterations) {
      m_Locks.Add(new ExclusiveSpinResourceLock());
      m_Locks.Add(new MonitorResourceLock());
      m_Locks.Add(new MutexResourceLock());
      m_Locks.Add(new NullResourceLock());
      m_Locks.Add(new OneManyResourceLock());
      //m_Locks.Add(new RecursionResourceLock(new OneManyResourceLock(), 10));
      m_Locks.Add(new OneManySpinResourceLock());
      //m_Locks.Add(new OneResourceLock());
      m_Locks.Add(new OptexResourceLock());
      //m_Locks.Add(new ReaderWriterSlimResourceLock());
      m_Locks.Add(new ReaderWriterResourceLock());
      m_Locks.Add(new EventResourceLock());
      m_Locks.Add(new SemaphoreResourceLock());
      if (Environment.OSVersion.Version >= new Version(6, 0, 0, 0)) {
         // If running on a Windows Vista or later OS
         m_Locks.Add(new Win32SlimResourceLock());
      }

      m_iterations = iterations;
      m_pt = new PerfTest_ResourceLock(m_iterations);
      m_ResultTimes = new TimeSpan[m_Locks.Count, 2, m_NumThreads.Length];
   }

   public void CalcResults() {
      String formatStr = "{0} Threads={1}, {2} Type={3}";
      for (Int32 rlIndex = 0; rlIndex < m_Locks.Count; rlIndex++) {
         for (Int32 threadIndex = 0; threadIndex < m_NumThreads.Length; threadIndex++) {
            foreach (Boolean write in new Boolean[] { false, true }) {
               Console.Write(formatStr, "Testing", m_NumThreads[threadIndex],
                   write ? "Writing" : "Reading", 
                   m_Locks[rlIndex].GetType().Name);

               TimeSpan result = m_pt.Test(write, m_NumThreads[threadIndex], 
                                           m_Locks[rlIndex]);

               Console.SetCursorPosition(0, Console.CursorTop);
               Console.WriteLine(formatStr, result, m_NumThreads[threadIndex],
                   write ? "Writing" : "Reading",
                   m_Locks[rlIndex].GetType().Name);
               m_ResultTimes[rlIndex, write ? 1 : 0, threadIndex] = result;
            }
         }
      }
   }

   public void ShowResultsByThreads() {
      for (Int32 threadIndex = 0; threadIndex < m_NumThreads.Length; threadIndex++) {
         List<string /> output = new List<string />(m_Locks.Count);
         for (Int32 rlIndex = 0; rlIndex < m_Locks.Count; rlIndex++) {
            output.Add(String.Format("{0}\t{1}\t{2}",
                m_ResultTimes[rlIndex, 0, threadIndex].ToString().Substring(6, 6),
                m_ResultTimes[rlIndex, 1, threadIndex].ToString().Substring(6, 6),
                m_Locks[rlIndex].GetType().Name));
         }
         output.Sort();
         Console.WriteLine();
         Console.WriteLine("Performance results where # " + 
                           "of threads={0} (ordered by read time)", 
                           m_NumThreads[threadIndex]);
         output.ForEach(delegate(String s) { Console.WriteLine(s); });
      }
   }

   public void ShowResultsByType(Char delimiter) {
      List<string> output = new List<string>(m_Locks.Count);
      for (Int32 rlIndex = 0; rlIndex < m_Locks.Count; rlIndex++) {
         StringBuilder sb = new StringBuilder(String.Format("{0,-30}", 
                                              m_Locks[rlIndex].GetType().Name));
         for (Int32 threadIndex = 0; threadIndex < m_NumThreads.Length; threadIndex++) {
            sb.AppendFormat("{0}{1}{0}{2}", delimiter,
               m_ResultTimes[rlIndex, 0, threadIndex].ToString().Substring(6, 6),
               m_ResultTimes[rlIndex, 1, threadIndex].ToString().Substring(6, 6));
         }
         output.Add(sb.ToString());
      }
      output.Sort();
      Console.WriteLine();
      Console.WriteLine("Performance results sorted by type");
      output.ForEach(Console.WriteLine);
   }
}

And the results are:

This tester app contains code which performs functional and performance
tests against the various classes in Wintellect's Power Threading Library.

The code in here can give you an idea of how to use some of these classes
but it is not the purpose of this code.

Also, please note that some of the classes in the Power Threading Library
require Windows Vista. Attempting to use these classes on a pre-Vista OS
will result in an exception such as EntryPointNotFoundException.

00:00:00.2256721 Threads=1, Reading Type=ExclusiveSpinResourceLock
00:00:00.2201972 Threads=1, Writing Type=ExclusiveSpinResourceLock
00:00:00.2355870 Threads=2, Reading Type=ExclusiveSpinResourceLock
00:00:00.2501346 Threads=2, Writing Type=ExclusiveSpinResourceLock
00:00:00.2854429 Threads=1, Reading Type=MonitorResourceLock
00:00:00.2908600 Threads=1, Writing Type=MonitorResourceLock
00:00:00.2875894 Threads=2, Reading Type=MonitorResourceLock
00:00:00.3314035 Threads=2, Writing Type=MonitorResourceLock
00:00:04.4873543 Threads=1, Reading Type=MutexResourceLock
00:00:04.5020571 Threads=1, Writing Type=MutexResourceLock
00:00:04.4240005 Threads=2, Reading Type=MutexResourceLock
00:00:04.4297671 Threads=2, Writing Type=MutexResourceLock
00:00:00.1643281 Threads=1, Reading Type=NullResourceLock
00:00:00.1647606 Threads=1, Writing Type=NullResourceLock
00:00:00.1707470 Threads=2, Reading Type=NullResourceLock
00:00:00.1672845 Threads=2, Writing Type=NullResourceLock
00:00:00.3112050 Threads=1, Reading Type=OneManyResourceLock
00:00:00.3198698 Threads=1, Writing Type=OneManyResourceLock
00:00:00.3060581 Threads=2, Reading Type=OneManyResourceLock
00:00:00.3166102 Threads=2, Writing Type=OneManyResourceLock
00:00:00.2598610 Threads=1, Reading Type=OneManySpinResourceLock
00:00:00.3128482 Threads=1, Writing Type=OneManySpinResourceLock
00:00:00.2723896 Threads=2, Reading Type=OneManySpinResourceLock
00:00:00.3116651 Threads=2, Writing Type=OneManySpinResourceLock
00:00:00.3156932 Threads=1, Reading Type=OptexResourceLock
00:00:00.2939886 Threads=1, Writing Type=OptexResourceLock
00:00:00.3136893 Threads=2, Reading Type=OptexResourceLock
00:00:00.2916697 Threads=2, Writing Type=OptexResourceLock
00:00:00.7522688 Threads=1, Reading Type=ReaderWriterResourceLock
00:00:00.7164296 Threads=1, Writing Type=ReaderWriterResourceLock
00:00:00.7309808 Threads=2, Reading Type=ReaderWriterResourceLock
00:00:00.7024473 Threads=2, Writing Type=ReaderWriterResourceLock
00:00:04.3859542 Threads=1, Reading Type=EventResourceLock
00:00:04.4718939 Threads=1, Writing Type=EventResourceLock
00:00:04.4373499 Threads=2, Reading Type=EventResourceLock
00:00:04.4778999 Threads=2, Writing Type=EventResourceLock
00:00:04.6628005 Threads=1, Reading Type=SemaphoreResourceLock
00:00:04.6602156 Threads=1, Writing Type=SemaphoreResourceLock
00:00:04.7259259 Threads=2, Reading Type=SemaphoreResourceLock
00:00:04.6945577 Threads=2, Writing Type=SemaphoreResourceLock
00:00:00.5937817 Threads=1, Reading Type=Win32SlimResourceLock
00:00:00.5360186 Threads=1, Writing Type=Win32SlimResourceLock
00:00:00.5944682 Threads=2, Reading Type=Win32SlimResourceLock
00:00:00.6398084 Threads=2, Writing Type=Win32SlimResourceLock

Sorted Results:

Performance results where # of threads=1 (ordered by read time)
00.164  00.164  NullResourceLock
00.225  00.220  ExclusiveSpinResourceLock
00.259  00.312  OneManySpinResourceLock
00.285  00.290  MonitorResourceLock
00.311  00.319  OneManyResourceLock
00.315  00.293  OptexResourceLock
00.593  00.536  Win32SlimResourceLock
00.752  00.716  ReaderWriterResourceLock
04.385  04.471  EventResourceLock
04.487  04.502  MutexResourceLock
04.662  04.660  SemaphoreResourceLock

Performance results where # of threads=2 (ordered by read time)
00.170  00.167  NullResourceLock
00.235  00.250  ExclusiveSpinResourceLock
00.272  00.311  OneManySpinResourceLock
00.287  00.331  MonitorResourceLock
00.306  00.316  OneManyResourceLock
00.313  00.291  OptexResourceLock
00.594  00.639  Win32SlimResourceLock
00.730  00.702  ReaderWriterResourceLock
04.424  04.429  MutexResourceLock
04.437  04.477  EventResourceLock
04.725  04.694  SemaphoreResourceLock

Performance results sorted by type
EventResourceLock               04.385  04.471  04.437  04.477
ExclusiveSpinResourceLock       00.225  00.220  00.235  00.250
MonitorResourceLock             00.285  00.290  00.287  00.331
MutexResourceLock               04.487  04.502  04.424  04.429
NullResourceLock                00.164  00.164  00.170  00.167
OneManyResourceLock             00.311  00.319  00.306  00.316
OneManySpinResourceLock         00.259  00.312  00.272  00.311
OptexResourceLock               00.315  00.293  00.313  00.291
ReaderWriterResourceLock        00.752  00.716  00.730  00.702
SemaphoreResourceLock           04.662  04.660  04.725  04.694
Win32SlimResourceLock           00.593  00.536  00.594  00.639

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

logicchild
Other Pref. Trust
United States United States
I started electronics training at age 33. I began studying microprocessor technology in an RF communications oriented program. I am 43 years old now. I have studied C code, opcode (mainly x86 and AT+T) for around 3 years in order to learn how to recognize viral code and the use of procedural languages. I am currently learning C# and the other virtual runtime system languages. I guess I started with the egg rather than the chicken. My past work would indicate that my primary strength is in applied mathematics.

Comments and Discussions

 
GeneralToo many code PinmemberEugene Sichkar20-Aug-09 5:04 
GeneralRe: Too many code Pinmentorlogicchild21-Aug-09 10:50 
QuestionI think there is an error in your code PinmemberMember 182981119-Aug-09 17:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 19 Aug 2009
Article Copyright 2009 by logicchild
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid