MSDN Magazine - March 2009 - (Page 32) Figure 1 Read File Asynchronously delegate byte[] AsyncOpenMethod(string filename); static byte[] AsyncOpen(string filename) { byte[] pixels = null; using (BinaryReader br = new BinaryReader(new FileStream(filename, FileMode.Open))) { Pixels = br.ReadBytes(4096); } } static void AsyncOpenTheFile(string filename) { byte[] pixels = null; AsyncOpenMethod aom = new AsyncOpenMethod(Class1.AsyncOpen); IAsyncResult iar = aom.BeginInvoke(filename, null, null); while (iar.IsCompleted == false) { // Do something? } pixels = aom.EndInvoke(iar); } out any further code required from the developer. Let’s put this into a real-world context. As part of a nightly maintenance cycle, a Web service must make copies of a number of files for easier storage offline for backup purposes. The F# code to perform this file copy, all completely asynchronously, looks like Figure 2. Here, both the read and write will be performed asynchronously, and the entire collection of tasks (one for each data file) will also be processed asynchronously. When you consider what this would take to do in C#, which would you rather write? (Chance Coble goes into vastly more detail about F# and asynchronous programming in “Easy Async: Build Concurrent Apps from Simple F# Expressions” in the October issue of MSDN Magazine at msdn.microsoft.com/magazine/cc967279.) Polyglot in Practice In practice, interoperating between F# and C# (or any other CLR language) is relatively straightforward, once the “shape” of the code (what the language turns into at the IL level) in both languages is well understood. On the surface this seems pretty easy—after all, how different can a method call be between two languages, given that the common language specification (CLS) dictates a lot of the otherwise-awkward issues, such as parameter placement, basic types, and byte ordering? Let’s put that to the test. Taking some simple F# code to start with, let’s compile it and turn it into a DLL and use ILDasm (the intermediate language disassembler, or Reflector, whichever tool you feel more comfortable with) to examine what it looks like. You can then graduate to more complicated F# expressions, such as the async workflow code Chance Coble presented in his October article, which I mentioned earlier. To begin with, take some simple F# code let x = 2 choice. Perhaps this kind of complex code would be simpler to write and maintain if you chose a functional language like F#, whose general tendencies towards immutability and zero side effects will prevent the need for explicit concurrency control, or a domain-specific language written in Ruby, which was designed to hide the concurrency details from the developers using it. To make this idea more concrete, imagine that a Web application needs to do some traditionally synchronous operation, such as performing some file-based I/O (or making a database or Web service call). Normally, the easiest thing to do from C# code will be to open the file via a traditional using statement, read the contents, store them to a byte array, then close the file, in a fashion similar to this: byte[] pixels = null BinaryReader br = new BinaryReader(new FileStream(filename, FileMode.Open)); pixels = br.ReadBytes(4096); It may be simple, but it’s also horribly serialized. No additional processing can happen on this thread while the file I/O operation is taking place. (For one simple file I/O operation, this is probably not a major concern, at least not until the number of these operations gets really large, which might happen as the site tries to scale up.) It would be better to read said file using the asynchronous operations available via the CLR thread pool, as you see in Figure 1. But the code that was simple before is now code that only a developer’s mother could love, and clearly deals with much more than merely reading a file. Now examine what writing a similar routine in F# would look like: async { use inStream = File.OpenRead(filename) let! pixels = inStream.AsyncRead(4096) } If we assume it lives in the default “F# Library” project file called Module.fs, the IL in Figure 3 (with a few things commented out to keep the IL listing brief) will be generated for it. If you trace the code through the IL, you notice two things; first, that the name “x” from the F# code is bound at the CLS level as a static property of a class called Module, and second, that its initial value of is not bound as a constant, but is initialized when the assembly is loaded, via the type constructor (.cctor) of the compiler-generated StartupCode$Library class. In other words, Figure 2 Make Backup Copies #light open System.IO let CopyFileAsync filename = async { use inStream = File.OpenRead(filename) let! pixels = inStream.AsyncRead(4096) use outStream = File.OpenWrite(filename + ".back") do! outStream.AsyncWrite(pixels) } let tasks = [ for i in 1 .. 10 -> CopyFileAsync("data" + i.ToString()) ] let taskResults = Async.Run (Async.Parallel tasks) I don’t want to dive too deeply into how the F# code does this, but the let! expression tells the F# compiler to generate the expression as an asynchronous expression, and the AsyncRead method is one that F# silently tacks on to the standard System.IO.Stream-derived classes through an F# language feature called extension methods. It effectively reads the file asynchronously and dumps the results into the byte array on the left-hand side of the expression, all with32 msdn magazine The Polyglot Programmer http://msdn.microsoft.com/magazine/cc967279
For optimal viewing of this digital publication, please enable JavaScript and then refresh the page. If you would like to try to load the digital publication without using Flash Player detection, please click here.