作者:kean
原文地址:http://through-the-interface.typepad.com/through_the_interface/2011/06/finally-working-with-the-async-ctp-for-vs-2010.html
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 }
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 PhotosynthProcAsyncCs.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. |