Finally working with the Async CTP for VS 2010

I've been planning to look at it for ages โ€“ and have certainly mentioned it before โ€“ but other things have kept on cropping up. Well last Thursday, on my train trip back from Wallisellen (the home of Microsoft Switzerland), I finally managed to take the plunge and start working with the Async CTP for Visual Studio 2010. I'd been in Wallisellen to attend an MSDN TechTalk presentation by Stephen Toub, Principal Architect on Microsoft's Parallel Computing Platform team. I've followed Stephen via his blog โ€“ and the Parallel Programming with .NET blog โ€“ for a long time, and thought it'd be a great opportunity to see him present in person (especially as such events in Switzerland tend to be quite intimate affairs โ€“ a great opportunity to have your questions answered).

I wasn't disappointed: Stephen really (and I mean really) knows his stuff, and I left the seminar inspired and itching to try some things out with the Async CTP. I can only hope that developers working with Autodesk technology get the same feeling when leaving one of my presentations (it's certainly a goal worth striving for, at least).

The ability to make asynchronous function calls easily is going to really significant for developers in .NET 5: I've been using F#'s excellent Asynchronous Workflows capability to do that, before โ€“ getting significant speed-ups on sequential/synchronous code โ€“ but having the capability integrated directly into the .NET Framework makes it even simpler for such a use-case (where there's an existing C# or VB.NET codebase). The basic premise is simple enough: when you call a synchronous method to get data over the wire (in particular, although you also get delays from accessing local storage) you have to wait for the results to come back. And if you're getting multiple files and processing each one, you're going to have significant overhead in your processing loop. The fact that synchronous calls block your UI is also significant: ideally you want to at least have the UI repaint โ€“ and probably show a progress bar โ€“ during such operations, rather than just going grey and showing the "Not responding" message in the application's title bar.

Until now, asynchronous calls were typically handled by separate threads and lots of manual coordination code: you typically wrap your code up into a delegate and attach it to an event called on completion of asynchronous method (basically a very manual continuation passing mechanism), which makes for messy, messy code.

The Async CTP (and presumably the same will be true of .NET 5) does all this under the covers: whenever you use the "await" keyword to wait for the results of an asynchronous call, execution continues in the outer, calling function โ€“ so it can continue to iterate through a loop, for instance โ€“ and the remaining code after the asynchronous call will get executed once the method returns. And that code will execute by default on the UI thread, which means you can update AutoCAD's UI without having to implement any special coordination capability (in F# I used a MailboxProcessor for this).

Which means that you can literally take synchronous code and add a few keywords (a combination of async and await) โ€“ and change your method calls to the usually-also-available Async versions โ€“ and the code will speed up significantly. You not only stop your code from blocking while waiting for asynchronous calls to complete, you make more efficient use of your CPU(s) as the work gets spread across whatever cores are available. And all this with minimal changes to your application's code. Too cool!

For now there are a few extra steps โ€“ such as adding a project reference to AsyncCtpLibrary.dll, which contains (no doubt among other things) asynchronous extension methods on System.Net.WebClient such as DownloadFileTaskAsync(), which creates an asynchronous task for which you can await completion โ€“ but these will disappear once the capabilities are integrated into the core .NET Framework. The steps for using the CTP are in this migration guide, which is well worth a look.

I went through the process for my BrowsePhotosynth application, which previous had three modes of operation: C# Synchronous, F# Synchronous and F# Asynchronous. It was really easy to add C# Asynchronous into the mix using the CTP.

Here's the new source file, with the lines where it differs from the original synchronous version in red:

    1 using Autodesk.AutoCAD.DatabaseServices;

    2 using Autodesk.AutoCAD.EditorInput;

    3 using Autodesk.AutoCAD.Runtime;

    4 using System.Threading.Tasks;

    5 using System.Net;

    6 using System.IO;

    7 using System;

    8 

    9 namespace PhotosynthProcAsyncCs

   10 {

   11   public class PointCloudProcessor

   12   {

   13     Editor _ed;

   14     ProgressMeter _pm;

   15     string _localPath;

   16 

   17     public PointCloudProcessor(

   18       Editor ed, ProgressMeter pm, string localPath

   19     )

   20     {

   21       _ed = ed;

   22       _pm = pm;

   23       _localPath = localPath;

   24     }

   25 

   26     public async Task<long> ProcessPointCloud(

   27       string path, int[] dims, string txtPath

   28     )

   29     {

   30       // Counter for the total number of points

   31 

   32       long totalPoints = 0;

   33 

   34       // Create our intermediate text file in the temp folder

   35 

   36       FileInfo t = new FileInfo(txtPath);

   37       StreamWriter sw = t.CreateText();

   38       using (sw)

   39       {

   40         // We'll use a web client to download each .bin file

   41 

   42         WebClient wc = new WebClient();

   43         using (wc)

   44         {

   45           for (int maj=0; maj < dims.Length; maj++)

   46           {

   47             for (int min=0; min < dims[maj]; min++)

   48             {

   49               // Loop for each .bin file

   50 

   51               string root =

   52                 maj.ToString() + "_" + min.ToString() + ".bin";

   53               string src = path + root;

   54               string loc = _localPath + root;

   55 

   56               try

   57               {

   58                 await wc.DownloadFileTaskAsync(src, loc);

   59               }

   60               catch

   61               {

   62                 return 0;

   63               }

   64 

   65               if (File.Exists(loc))

   66               {

   67                 // Open our binary file for reading

   68 

   69                 BinaryReader br =

   70                   new BinaryReader(

   71                     File.Open(loc, FileMode.Open)

   72                   );

   73                 using (br)

   74                 {

   75                   try

   76                   {

   77                     // First information is the file version

   78                     // (for now we support version 1.0 only)

   79 

   80                     ushort majVer = ReadBigEndianShort(br);

   81                     ushort minVer = ReadBigEndianShort(br);

   82 

   83                     if (majVer != 1 || minVer != 0)

   84                     {

   85                       _ed.WriteMessage(

   86                         "\nCannot read a Photosynth point " +

   87                         "cloud of this version ({0}.{1}).",

   88                         majVer, minVer

   89                       );

   90                       return 0;

   91                     }

   92 

   93                     // Clear some header bytes we don't use

   94 

   95                     int n = ReadCompressedInt(br);

   96                     for (int i = 0; i < n; i++)

   97                     {

   98                       int m = ReadCompressedInt(br);

   99 

  100                       for (int j = 0; j < m; j++)

  101                       {

  102                         ReadCompressedInt(br);

  103                         ReadCompressedInt(br);

  104                       }

  105                     }

  106 

  107                     // Find the number of points in the file

  108 

  109                     int numPoints = ReadCompressedInt(br);

  110                     totalPoints += numPoints;

  111 

  112                     _ed.WriteMessage(

  113                      "\nProcessed points_{0} containing {1} " +

  114                      "points.",

  115                       root, numPoints

  116                     );

  117 

  118                     for (int k = 0; k < numPoints; k++)

  119                     {

  120                       // Read our coordinates

  121 

  122                       float x = ReadBigEndianFloat(br);

  123                       float y = ReadBigEndianFloat(br);

  124                       float z = ReadBigEndianFloat(br);

  125 

  126                       // Read and extract our RGB values

  127 

  128                       UInt16 rgb = ReadBigEndianShort(br);

  129 

  130                       int r = (rgb >> 11) * 255 / 31;

  131                       int g = ((rgb >> 5) & 63) * 255 / 63;

  132                       int b = (rgb & 31) * 255 / 31;

  133 

  134                       // Write the point with its color to file

  135 

  136                       sw.WriteLine(

  137                         "{0},{1},{2},{3},{4},{5}",

  138                         x, y, z, r, g, b

  139                       );

  140                     }

  141                   }

  142                   catch (System.Exception ex)

  143                   {

  144                     _ed.WriteMessage(

  145                      "\nError processing point cloud file " +

  146                      "\"points_{0}\": {1}",

  147                      root, ex.Message

  148                     );

  149                   }

  150                 }

  151 

  152                 // Delete our local .bin file

  153 

  154                 File.Delete(loc);

  155 

  156                 // Show some progress

  157 

  158                 _pm.MeterProgress();

  159                 System.Windows.Forms.Application.DoEvents();

  160 

  161                 if (

  162                   PhotosynthImporter.Commands.CheckEscape(_ed)

  163                 )

  164                 {

  165                   return 0;

  166                 }

  167               }

  168             }

  169           }

  170         }

  171       }

  172       return totalPoints;

  173     }

&#
160; 174
 

  175     private static int ReadCompressedInt(BinaryReader br)

  176     {

  177       int i = 0;

  178       byte b;

  179 

  180       do

  181       {

  182         b = br.ReadByte();

  183         i = (i << 7) | (b & 127);

  184       }

  185       while (b < 128);

  186 

  187       return i;

  188     }

  189 

  190     private static float ReadBigEndianFloat(BinaryReader br)

  191     {

  192       byte[] b = br.ReadBytes(4);

  193       return BitConverter.ToSingle(

  194         new byte[] { b[3], b[2], b[1], b[0] },

  195         0

  196       );

  197     }

  198 

  199     private static UInt16 ReadBigEndianShort(BinaryReader br)

  200     {

  201       byte b1 = br.ReadByte();

  202       byte b2 = br.ReadByte();

  203 

  204       return (ushort)(b2 | (b1 << 8));

  205     }

  206   }

  207 }

The calling code also ended up being very similar to the C# Synchronous implementation โ€“ the only difference being we receive a Task<long> back rather than a long (hopefully it's also safe to just extract the Result from the Task โ€“ at least it worked well in my various tests):

PhotosynthProcAsyncCs.PointCloudProcessor pcp =

  new PhotosynthProcAs
yncCs.
PointCloudProcessor(

    ed, pm, localPath

  );

 

System.Threading.Tasks.Task<long> task =

  pcp.ProcessPointCloud(path, dims, txtPath);

totalPts = task.Result;

I know I'll get asked whether I still prefer F# Asynchronous Workflows to the Async CTP capabilities. On the one hand, I certainly like the purity of describing tasks in F#'s more declarative style โ€“ this definitely appeals to me โ€“ but on the other hand (which must be the more pragmatic of the two ๐Ÿ™‚ the fact you can integrate this capability with so few modified lines of C# code is astonishing. I'll definitely keep space in my toolbox for both approaches.

In terms of performance, the C# Asynchronous implementation is equivalent to F# Asynchronous (and both are 8-10X quicker than their synchronous siblings, for this particular implementation scenario). I think I'm going to put together a quick screen-cam video to show this in action. I'll hopefully post something in the next few days.

4 responses to “Finally working with the Async CTP for VS 2010”

  1. Hi Kean

    I might be misunderstanding something here, but how do you use asynchronous logic with AutoCAD? My understanding is that AutoCAD ensures that from a plugin's perspective, everything is singularly threaded?

    Regards

    Gavin Glynn ๐Ÿ™‚

  2. Hi Gavin,

    This particular code is retrieving data from the web, before it gets combined and imported into AutoCAD using standard commands.

    As the code that executes after the await calls runs by default on the UI thread (from which it's safe to call into AutoCAD), it should work very well: you just need to be careful not to create some kind of asychronous method that attempts to do something directly inside AutoCAD. That would fail and probably quite nastily.

    Regards,

    Kean

  3. Scott McFarlane Avatar
    Scott McFarlane

    It is indeed astonishing how few code changes were necessary to make this work. As we wait for .NET 5, I have found Parallel LINQ (PLINQ) quite useful for tasks like this. I'm too lazy to learn F# at this point.

  4. I hear that. well not too lazy but too busy for a full mental shift - yet f# looks fascinating, when you have the time...

Leave a Reply to Gavin Glynn Cancel reply

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