Continued from Willy’s Cave Dwelling Notes #3 – C# … re-visiting some interesting features and a quick de-tour through Willy’s Cave Dwelling Notes #4 – Robert MacLean and co. poster and other gems we switch context to asynchronous programming.

Code used in this post can be downloaded from: Supporting Guidance and Whitepapers.
IMPORTANT: The sample code only focuses on key aspects, is not intended for production and intentionally does not implement error and exception handling code.


Scenario

The scenario I will try and repro in this sample is our typical weekend dilemma.  We start our weekend chores as a family, one focuses on the washing, one on the lawn and two of us on the weeds and general maintenance tasks. Once we are all done we get some food, popcorn and movies. Visually this looks like:

image


Synchronous Programming

image

The code for the synchronous weekend event could be written as:

   1: // Copyright © Microsoft Corporation.  All Rights Reserved.
   2: // This code released under the terms of the 
   3: // Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
   4: // This is sample code only, do not use in production environments
   5: //
   6: // Part of Visual Studio ALM Rangers - Willy's Cave Dwelling Journals
   7:  
   8: using System;
   9: using System.Collections.Generic;
  10: using System.Linq;
  11: using System.Text;
  12: using System.Threading.Tasks;
  13: using System.Threading;
  14:  
  15: namespace Microsoft.ALMRangers.Samples.CSharpFeatureTour.Asynchronous
  16: {
  17:     public class SynchronousTasks
  18:     {
  19:         public bool StartWork()
  20:         {
  21:             Console.ForegroundColor = ConsoleColor.Red;
  22:             Console.WriteLine();
  23:             Console.WriteLine("Starting the weekend chores synchronously!");
  24:             Console.WriteLine("------------------------------------------");
  25:             Console.ForegroundColor = ConsoleColor.Green;
  26:             return true;
  27:         }
  28:  
  29:         public bool DoWork(int milliSeconds)
  30:         {
  31:             Console.WriteLine("\t\tStart Sleep: {0}", milliSeconds);
  32:             Thread.Sleep(milliSeconds);
  33:             Console.WriteLine("\t\tDone Sleeping: {0}", milliSeconds);
  34:             return true;
  35:         }
  36:  
  37:         public bool DoLawn()
  38:         {
  39:             Console.WriteLine("\tStarting with the Lawn");
  40:             this.DoWork(Program.timeLawn);
  41:             Console.WriteLine("\tFinished with the Lawn");
  42:             return true;
  43:         }
  44:  
  45:         public bool DoWeeds()
  46:         {
  47:             Console.WriteLine("\tStarting with the Weeds");
  48:             this.DoWork(Program.timeWeeds);
  49:             Console.WriteLine("\tFinished with the Weeds");
  50:             return true;
  51:         }
  52:  
  53:         public bool DoWashing()
  54:         {
  55:             Console.WriteLine("\tStarting with the Washing");
  56:             this.DoWork(Program.timeWashing);
  57:             Console.WriteLine("\tFinished with the Washing");
  58:             return true;
  59:         }
  60:  
  61:         public bool DoAllWork()
  62:         {
  63:             var dateTimeStart = DateTime.Now;
  64:             Console.WriteLine("\tStarting the chores @ {0}", dateTimeStart.ToString());
  65:             DoLawn();
  66:             DoWeeds();
  67:             DoWashing();
  68:             var dateTimeStop = DateTime.Now;
  69:             Console.WriteLine("\tFinished the chores @ {0}", dateTimeStop.ToString());
  70:  
  71:             var timeSpan = dateTimeStop - dateTimeStart;
  72:             Console.WriteLine("\tProcessed for ----> {0}.{1} seconds :)", timeSpan.Seconds, timeSpan.Milliseconds);
  73:  
  74:             return true;
  75:         }
  76:     }
  77: }

There is nothing special about this code. When we run the program we see the following result, which shows synchronous execution as expected:
image

Are you wondering if the lawn, the weeds and the washing cannot be done in parallel as there are no dependencies? Welcome to asynchronous weekend chores and an opportunity for us to explore the C#5 asynchronous features Smile


Asynchronous Programming

I have been writing multi-threaded services for a long time. In the 80’s my adventure was an encryption service sitting on top of the CTOS file system, followed by a multi-threaded CTOS based service pooling 256 LU0 connections and managing the communication to the IBM mainframe. In the late 90’s the CTOS service was migrated to Windows NT, introducing me to Win32 and its ingenious event object synchronization and multi-threading. As .NET emerged, the asynchronous programming turned to worker threads and later thread pools. What I remember were fun and interesting times when we designed and developed these multi-threaded services, but also sheer hell, pain and loooooooooooooooooooooooooooong nights when we had to debug 256 threads to find a corruption or lock.

To cut a long story short, we could look at the .NET threads, concurrent, multithreaded and parallel programming … but we will explore asynchronous programming supported by C# 5 instead Smile

Exploring the C# Asynchronous Example

We should differentiate between asynchronous and parallel programming at this stage. While we use asynchronous features in the following example, we are actually doing parallel programming by dividing up the weekend chores and trying to complete them simultaneously.

image

The code for the asynchronous weekend event could be written as:

   1: // Copyright © Microsoft Corporation.  All Rights Reserved.
   2: // This code released under the terms of the 
   3: // Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
   4: // This is sample code only, do not use in production environments
   5: //
   6: // Part of Visual Studio ALM Rangers - Willy's Cave Dwelling Journals
   7:  
   8: using System;
   9: using System.Collections.Generic;
  10: using System.Linq;
  11: using System.Text;
  12: using System.Threading.Tasks;
  13: using System.Threading;
  14:  
  15: namespace Microsoft.ALMRangers.Samples.CSharpFeatureTour.Asynchronous
  16: {
  17:     public class AsynchronousTasks
  18:     {
  19:         public bool StartWork()
  20:         {
  21:             Console.ForegroundColor = ConsoleColor.Red;
  22:             Console.WriteLine();
  23:             Console.WriteLine("Starting the weekend chores asynchronously!");
  24:             Console.WriteLine("------------------------------------------");
  25:             Console.ForegroundColor = ConsoleColor.Green;
  26:             return true;
  27:         }
  28:  
  29:         public async Task<bool> DoWork(int milliSeconds)
  30:         {
  31:             //Task  sleeperTask = Task.Factory.StartNew(()=>Thread.Sleep(milliSeconds));
  32:             //await sleeperTask;
  33:  
  34:             Action<object> action = (object interval) =>
  35:             {
  36:                 Console.WriteLine("\t\tStart Sleep: {0}", interval);
  37:                 Thread.Sleep((int)interval);
  38:                 Console.WriteLine("\t\tDone Sleeping: {0}", interval);
  39:             };
  40:  
  41:             // Construct an unstarted task
  42:             Task sleeperTask = new Task(action, milliSeconds);
  43:             sleeperTask.Start();
  44:             await sleeperTask;
  45:             
  46:             return true;
  47:         }
  48:  
  49:         public async Task<bool> DoLawn()
  50:         {
  51:             Console.WriteLine("\tStarting with the Lawn");
  52:             //var t = this.DoWork(Program.timeLawn);
  53:             await this.DoWork(Program.timeLawn);
  54:             Console.WriteLine("\tFinished with the Lawn");
  55:             return true;
  56:         }
  57:  
  58:         public async Task<bool> DoWeeds()
  59:         {
  60:             Console.WriteLine("\tStarting with the Weeds");
  61:             await this.DoWork(Program.timeWeeds);
  62:             Console.WriteLine("\tFinished with the Weeds");
  63:             return true;
  64:         }
  65:  
  66:         public async Task<bool> DoWashing()
  67:         {
  68:             Console.WriteLine("\tStarting with the Washing");
  69:             await this.DoWork(Program.timeWashing);
  70:             Console.WriteLine("\tFinished with the Washing");
  71:             return true;
  72:         }
  73:  
  74:         public bool DoAllWork()
  75:         {
  76:             // Scenario 1 - Async
  77:             {
  78:                 Console.WriteLine("\tScenario 1 - Async --------------------");
  79:                 Console.WriteLine();
  80:                 
  81:                 var dateTimeStart = DateTime.Now;
  82:                 Console.WriteLine("\tStarting the chores @ {0}", dateTimeStart.ToString());
  83:                 var t1 = DoLawn();
  84:                 var t2 = DoWeeds();
  85:                 var t3 = DoWashing();
  86:                 Task.WaitAll(t1, t2, t3);
  87:                 var dateTimeStop = DateTime.Now;
  88:                 Console.WriteLine("\tFinished the chores @ {0}", dateTimeStop.ToString());
  89:  
  90:                 var timeSpan = dateTimeStop - dateTimeStart;
  91:                 Console.WriteLine("\tProcessed for ----> {0}.{1} seconds :)", timeSpan.Seconds, timeSpan.Milliseconds);
  92:             }
  93:  
  94:             // Scenario 2 - Run Async Sync
  95:             {
  96:                 Console.WriteLine(); 
  97:                 Console.WriteLine("\tScenario 2 - Run Async Syncc ----------");
  98:                 Console.WriteLine();
  99:  
 100:                 var dateTimeStart = DateTime.Now;
 101:                 Console.WriteLine("\tStarting the chores @ {0}", dateTimeStart.ToString());
 102:                 DoLawn().Wait();
 103:                 DoWeeds().Wait();
 104:                 DoWashing().Wait();
 105:                 var dateTimeStop = DateTime.Now;
 106:                 Console.WriteLine("\tFinished the chores @ {0}", dateTimeStop.ToString());
 107:  
 108:                 var timeSpan = dateTimeStop - dateTimeStart;
 109:                 Console.WriteLine("\tProcessed for ----> {0}.{1} seconds :)", timeSpan.Seconds, timeSpan.Milliseconds);
 110:             }
 111:  
 112:  
 113:             return true;
 114:         }
 115:     }
 116: }

Exploring the code before we hit (click) the run button:

  • DoAllWork() has two scenarios.
    • Lines 76-92 implements the asynchronous weekend.
    • Lines 94-110 simulate our synchronous example by running the asynchronous logic synchronously.
  • Scenario 1 – Run Async
    • Lines 83 to 85 we call the asynchronous versions of our methods, all of which return a Task.
    • Line 86 has a Task.WaitAll() statement which halts the bus until all of the tasks complete.
  • Lines 29-47 implements an asynchronous version of the DoWork().
    • We add the async keyword, which notifies the compiler that the method it modifies is asynchronous.
    • We return a Task<> to implement the Task-Based Asynchronous Pattern.
    • We define an action, which performs a Sleep (37), create (42) and start (43) a new Task.
    • Line 44 has the next magic, the await operator. This tells the compiler to suspend the execution of the method until the awaited task completes. The compiler implements magic behind the scenes by returning to the caller and resuming the async method where it left off when the task completes.

If you compare the synchronous and asynchronous code you will notice that very few changes were necessary to make this code act asynchronously.

When we run the code we get the following result:
image

As expected the tasks of scenario 1 run in parallel, take less elapsed time overall and the Task.WaitAll() ensures that the workers all meet in the kitchen to plan the rest of the weekend.

Finally we peek into the IL

Looking at the synchronous DoWeeds() method we see the following IL code being generated:

image

Switching to the asynchronous DoWeeds() method, we immediately see that the compiler has done some fancy footwork behind the scene, in reaction to the async and await keywords.

image


Summarizing the important keywords and statements

Keyword/Statement C#/TPL Why important?
async C# Indicator that the method, lambda expression or anonymous method is asynchronous.
await C# “Await” … suspend the execution of the method until the awaited task completes.
Lambda Expression C# Anonymous block of code. See Getting to grips with C# Lambda ... a random vacation thread that drove me crazy for more details.
Syntax: (parameters) => { code }
Task TPL

Task defines a unit of work, represented by the TPL Task class.

  • A thread is dedicated to the Task until it completes.
  • Multiple Task, i.e. parallel programming makes real sense if you have >1 cores or alternatively a task that does a lot of waiting.
Task.ContinueWith TPL Create a continuation from one task to the next.
Task.Start TPL

Start the task. Caution when dealing with user interface, which is only allowed to run within the context of the main message pump context.

  • Task.Start(TaskSchedule.FromCurrentSynchronizationContext) … would run the task on the same thread. Not effective.
  • Split computational and user interface tasks into separate tasks. Consider using Task.ContinueWith(…) to queue the separate tasks.
Task.WaitAll TPL Wait for all tasks to complete.
Task.WaitAny TPL

Wait for any of the tasks to complete. Useful to implement the Wait for tasks one by one:

  1. List<Task<…>> tasks = new List< Task<…> >()
  2. // add Task to list as you create them: tasks.Add ( Task.Factory.StartNew<…>(…) );
  3. while ( tasks.Count > 0)
  4. {
  5.   int idx = Task.WaitAny ( tasks.ToArray() );
  6.   … process result, etc.
  7.   tasks.RemoveAt(idx);
  8. }
  TPL Task Parallel Library, which emerged with .NET 4. See http://msdn.microsoft.com/en-us/library/dd460717.aspx for details.
     

Looking for more? See Executing MSBuild Targets in Parallel by Michael Fourie.