Multithreading .NET Apps for Optimal Performance

by Eric Bergman-Terrell



Listing One



class ThreadTest

{

  const int iterations = 100;

  public void HelloWorld1()

  {

    for (int i = 1; i <= iterations; i++)

    {

      Console.WriteLine("Hello World1 {0}", i);

    }

    Console.WriteLine("\nHello World1 finished\n");

  }

  public void HelloWorld2()

  {

    for (int i = 1; i <= iterations; i++)

    {

      Console.WriteLine("Hello World2 {0}", i);

    }

    Console.WriteLine("\nHello World2 finished\n");

  }

  [STAThread]

  static void Main(string[] args)

  {

    ThreadTest threadTest = new ThreadTest();

    // Create the threads. The ThreadStart delegate must

    // refer to a void method with no parameters.

    Thread thread1 = 

        new Thread(new ThreadStart(threadTest.HelloWorld1));

    Thread thread2 = 

        new Thread(new ThreadStart(threadTest.HelloWorld2));

    // Start the threads.

    thread1.Start();

    thread2.Start();

    // Wait for threads to complete. Doesn't matter which thread finishes first.

    thread1.Join();

    thread2.Join();

    Console.WriteLine("Main finished");

  }

}





Listing Two



class AsynchMethodTest

{

  const int iterations = 100;

  private delegate void WorkDelegate(string message);

  private void Work(string message)

  {

    for (int i = 1; i <= iterations; i++)

    {

      Console.WriteLine(message + " " + i);

    }

    Console.WriteLine("\n" + message + " finished\n");

  }

  static void Main(string[] args)

  {

    AsynchMethodTest test = new AsynchMethodTest();

    WorkDelegate workDelegate = new WorkDelegate(test.Work);

    WaitHandle[] waitHandles = new WaitHandle[2];

    waitHandles[0] = workDelegate.BeginInvoke("Hello World1", null,

                               null).AsyncWaitHandle;

    waitHandles[1] = workDelegate.BeginInvoke("Hello World2", null,

                               null).AsyncWaitHandle;

    // Asynchrononous methods are run on background threads by default.

    // Wait for them to complete before letting the main thread exit.

    WaitHandle.WaitAll(waitHandles);



    Console.WriteLine("Main finished");

  }

}





Listing Three



public static void SearchAllFolders(string[] allFolders, 

       string searchPattern, string containingText, 

       bool regularExpression, bool caseSensitive)

{

  SearchInfo.StartTime = DateTime.Now;

  SearchFoldersDelegate searchFoldersDelegate = 

    new SearchFoldersDelegate(SearchFolders);

  AsyncCallback asyncCallback = new AsyncCallback(SearchCallback);

  SearchInfo.Cancelled  = false;

  SearchInfo.InProgress = true;

  // If user requested a multithreaded search...

  if (SerializeConfiguration.Settings.MultithreadedSearch)

  {

    // Don't want to thrash hard drives and optical drives. 

    // Ensure that each drive is only accessed by one thread.

    Hashtable drives = CreateDriveHashtable(allFolders);

    // Keep track of how many searches will be done.

    SearchInfo.totalSearches.Set(drives.Keys.Count);

    SearchInfo.remainingSearches.Set(drives.Keys.Count);

    // For each drive being searched...

    foreach (string drive in drives.Keys)

    {

      // Get the folders on the drive to be searched.

      ArrayList foldersArrayList = (ArrayList) drives[drive];

      // Convert ArrayList of folders to a string array.

      string[] folders = (string[]) foldersArrayList.ToArray(typeof(string));

      // Call the asynchronous method with all the search parameters.

      searchFoldersDelegate.BeginInvoke(folders, searchPattern,

       containingText, regularExpression,caseSensitive, asyncCallback, drive);

    }

  }

  else // If user requested a single-threaded search...

  {

    SearchInfo.totalSearches.Set(1);

    SearchInfo.remainingSearches.Set(1);

    searchFoldersDelegate.BeginInvoke(allFolders, searchPattern,

      containingText, regularExpression, caseSensitive, 

      asyncCallback, "all folders");

  }

  // It's OK to directly manipulate the status bar because

  // this method was called from the GUI thread.

  Globals.mainForm.UpdateStatusBar("Searching...");

}





Listing Four



delegate void UpdateStatusBarDelegate(String Text);

 ...

delegate void UpdateProgressBarDelegate();

private static void SearchCallback(IAsyncResult asynchResult)

{

  // One parallel search just completed, so decrement

  // the number of remaining searches.

  int remainingSearches = SearchInfo.remainingSearches.Decrement();

  UpdateStatusBarDelegate USBD = 

    new UpdateStatusBarDelegate(Globals.mainForm.UpdateStatusBar);

  // If the search was not cancelled...

  if (!SearchInfo.Cancelled)

  {

    string message = string.Format("Finished searching {0}", 

                    asynchResult.AsyncState);

    // Update the status bar with a progress message.

    Globals.mainForm.Invoke(USBD, new object[] { message } );

  }

  // If the last asynchronous method finished searching...

  if (remainingSearches == 0)

  {

    TimeSpan elapsedTime = DateTime.Now - SearchInfo.StartTime;

    // Update status bar to display total search time.

    Globals.mainForm.Invoke(USBD, 

      new object[] { SearchInfo.Cancelled ? "Search cancelled" : 

      "Search completed." + " Elapsed time: " + elapsedTime.ToString() });

    SearchInfo.InProgress = false;

  }

  // Update the progress bar.

  UpdateProgressBarDelegate UPD = new

    UpdateProgressBarDelegate(Globals.mainForm.UpdateProgressBar);

  // You don't need to pass an object array to Invoke when the

  // method (UpdateProgressBar) does not have any parameters.

  Globals.mainForm.Invoke(UPD);

}





Listing Five



// A ThreadSafeCounter contains an integer value that can be read, written, 

// incremented and decremented from multiple threads without data corruption 

// issues caused by race conditions.

public sealed class ThreadSafeCounter

{

  private int intValue;

  public ThreadSafeCounter()

  {

    intValue = 0;

  }

  public ThreadSafeCounter(int intValue)

  {

    this.intValue = intValue;

  }

  [MethodImpl(MethodImplOptions.Synchronized)]

  public int Get()

  {

    return intValue;

  }

  [MethodImpl(MethodImplOptions.Synchronized)]

  public int Set(int newValue)

  {

    int result = intValue;

    intValue = newValue;

    return result;

  }

  public int Increment()

  {

    return Interlocked.Increment(ref intValue);

  }

  public int Decrement()

  {

    return Interlocked.Decrement(ref intValue);

  }

}





4



