Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « Coming to AU 2008? Vote to come along and let us know what you need from the AutoCAD .NET API! | Main | A video introduction »

    October 27, 2008

    AU Handouts: AutoCAD® .NET - Developing for AutoCAD® Using F# - Part 2

    This post continues on from Part 1 of this series. You'll find much of this content has been used before in these previous posts, although post does include content updated for F# 1.9.6.2 (the September 2008 CTP).

    The first thing we need to do is – as with any AutoCAD .NET project – add project references to AutoCAD’s managed assemblies, acmgd.dll and acdbmgd.dll. With F#’s integration into Visual Studio 2008 you do this in exactly the same way as you would for a C# or VB.NET project, by selecting Project -> Add Reference... from the pull-down menu or right-clicking the project inside the Solution Explorer and selecting Add Reference... from the context menu.

    Here you then browse to the AutoCAD 2009 folder and filter for *mgd* files (at least this is the way I do it), and select the two we want:

    Add project references to AutoCAD's managed assemblies

    Figure 10 – Adding project references to AutoCAD’s managed assemblies

    Now we need to make sure AutoCAD recognizes a module within a namespace, from which it is able to load commands. I found – by using .NET Reflector – that the appropriate structure is to declare your functions as the contents of a module (this needs to come after the #light directive):

    module MyNamespace.MyApplication

    Next we’re going to specify the .NET namespaces we’ll be using inside this application:

    open Autodesk.AutoCAD.Runtime

    open Autodesk.AutoCAD.ApplicationServices

    open Autodesk.AutoCAD.DatabaseServices

    We’ll then skip past our definitions of words and sortedWords, and define our command function:

    [<CommandMethod("Words")>]

    let listWords () =

      // Let's get the usual helpful AutoCAD objects

      let doc =

        Application.DocumentManager.MdiActiveDocument

      let ed = doc.Editor

      let db = doc.Database

      // "use" has the same effect as "using" in C#

      use tr =

        db.TransactionManager.StartTransaction();

      // Get appropriately-typed BlockTable and BTRs

      let bt =

        tr.GetObject

          (db.BlockTableId,OpenMode.ForRead)

        :?> BlockTable

      let ms =

        tr.GetObject

          (bt.[BlockTableRecord.ModelSpace],

           OpenMode.ForRead)

        :?> BlockTableRecord

      let ps =

        tr.GetObject

          (bt.[BlockTableRecord.PaperSpace],

           OpenMode.ForRead)

        :?> BlockTableRecord

    Most of this section should be familiar to anyone who has used the .NET API to AutoCAD – there are really only a couple of ideas that may need explanation:

    1. The use keyword is just like C#’s using – but we don’t use curly braces to define scope. The scope gets defined as the remainder of the function in which the use statement has been used. Once the function completes the used object will be disposed of automatically.
    2. We’re using the dynamic cast operator (:?>) to specify the type of object we’re opening with GetObject(). This operator involves a query to check whether this is valid – if we wanted to do a static cast we could use :> instead.

    Now we have opened our modelspace and paperspace objects (we could go further and open other layouts, but – once again – I’ll leave that as a follow-on exercise for those who feel the need to do it :-) we can look at the code we need to extract the text from our database-resident objects.

    Let’s start by defining a local function which takes an ObjectId and uses it to open an object, and for a textual object (DBText or MText) it will return its contents:

      // A function that accepts an ObjectId and returns

      // a list of the text contents, or an empty list.

      // Note the valid use of tr, as it is in scope

      let extractText x =

        let obj = tr.GetObject(x,OpenMode.ForRead)

        match obj with

        | :? MText as m -> m.Contents

        | :? DBText as d -> d.TextString

        | _ -> ""

    Once again we haven’t specified the type of the argument – this will be inferred by the system – but we could very easily do so. We’re using the transaction previously started in the listWords function – the reason for defining extractText local to it – which is quite valid, as it’s in scope.

    After opening the object for read from its ID we’re using pattern-matching – a technique that is a huge timesaver for functional programmers – to check on the type of the object and return the appropriate property of it. This is just like a much cleaner switch statement in C#.

    We could choose to match against any property of the object, but in our case we want to check the type, so use this operator: :?. The as keyword is a syntactic shortcut that defines a value we can then use to easily dereference the object and get at its properties and methods.

    The final clause of the three is a wildcard: it will match all object that are not DBText or MText objects and return an empty string.
    Now that we can get at the contents of our text objects, let’s write a quick recursive function to display the contents of the final list of words inside AutoCAD:

      // A recursive function to print the contents of a list

      let rec printList x =

        match x with

        | h :: t -> ed.WriteMessage("\n" + h); printList t

        | [] -> ed.WriteMessage("\n")

    Once again, this is a local function, so using our Editor (accessed via the ed value) is quite valid. We’re using pattern-matching again to create a recursive function (indicated via the rec keyword and then the recursive call to printList). When we find an empty list ([]) we simply print a newline, but when we find a list with a head (h) and a tail (t – which may well end up being empty, by the way, we’ll find out the next time we recurse into printList), we print the head and recurse with the tail.

    One thing to look out for when defining recursive functions: they really need to be defined as tail-recursive, which means that the recursive call should be the last operation. This allows the compiler to perform tail call optimization, which replaces the declared recursion with a simply while loop inside the generated code.

    Why does this matter? Well, calling a function does have some overhead, as stack space is required to store information about the function and its arguments, so if we have a list of 10,000 words to print and the function hasn’t been optimized, the recursion could cause problems. (The number could be 100,000 or 1,000,000, but the point is there is a number).

    The above code does get optimized properly (even if the pattern for the empty list comes after the recursive call – it’s really about the position of the recursive call in the clause that recurses, rather than the overall program), and this is easy to check with .NET Reflector. In fact there’s an article on my blog covering just this:

    http://through-the-interface.typepad.com/through_the_interface/2008/02/using-reflector.html

      Seq.to_list (Seq.cast ms) @    // Create a list of modelspace ids,

        Seq.to_list (Seq.cast ps) |> //  appending those from paperspace

        List.map extractText |>      // Extract the text from each object

        sortedWords |>               // Get a sorted, canonical list of words

        printList                    // Print the resultant words

    A couple of comments on the above plumbing: ms and ps are both IEnumerable types (which correspond to the Seq class in F#), but are both untyped. This means we have to cast them, to be able to access them properly from F#, and then we can simply call Seq.to_list to get the contents into a list. The @ operator appends the list of ObjectIds of objects in modelspace with those in paperspace, and we then pipe the list into a call to List.map which runs our extractText function on all the objects in the combined list. The results get piped into our sortedWords function, and we finally print them to the command-line using our recursive printList function.

    Finally, we’re just going to call commit on our transaction object, as for performance reasons this is currently best practice:

      // As usual, committing is cheaper than aborting

      tr.Commit()

    That’s it for our first AutoCAD application. Let’s see the entire listing:

    #light

    // Declare a specific namespace and module name

    module MyNamespace.MyApplication

    open Autodesk.AutoCAD.Runtime

    open Autodesk.AutoCAD.ApplicationServices

    open Autodesk.AutoCAD.DatabaseServices

    // Partial application of split which can then be

    // applied to a string to retrieve the contained words

    let words =

      let seps = " \t~`!@#$%^&*()-=_+{}|[]\\;':\"<>?,./"

      seps.ToCharArray() |> Array.to_list |> String.split

    let sortedWords x =

        List.map words x |>  // Get the words from each string

        List.concat |>       // No need for the outer list

        Set.of_list |>       // Create a set from the list

        Set.to_list          // Create a list from the set

    // Now we define our command

    [<CommandMethod("Words")>]

    let listWords () =

      // Let's get the usual helpful AutoCAD objects

      let doc =

        Application.DocumentManager.MdiActiveDocument

      let ed = doc.Editor

      let db = doc.Database

      // "use" has the same effect as "using" in C#

      use tr =

        db.TransactionManager.StartTransaction();

      // Get appropriately-typed BlockTable and BTRs

      let bt =

        tr.GetObject

          (db.BlockTableId,OpenMode.ForRead)

        :?> BlockTable

      let ms =

        tr.GetObject

          (bt.[BlockTableRecord.ModelSpace],

           OpenMode.ForRead)

        :?> BlockTableRecord

      let ps =

        tr.GetObject

          (bt.[BlockTableRecord.PaperSpace],

           OpenMode.ForRead)

        :?> BlockTableRecord

      // Now the fun starts...

      // A function that accepts an ObjectId and returns

      // a list of the text contents, or an empty list.

      // Note the valid use of tr, as it is in scope

      let extractText x =

        let obj = tr.GetObject(x,OpenMode.ForRead)

        match obj with

        | :? MText as m -> m.Contents

        | :? DBText as d -> d.TextString

        | _ -> ""

      // A recursive function to print the contents of a list

      let rec printList x =

        match x with

        | h :: t -> ed.WriteMessage("\n" + h); printList t

        | [] -> ed.WriteMessage("\n")

      // And here's where we plug everything together...

      Seq.to_list (Seq.cast ms) @    // Create a list of modelspace ids,

        Seq.to_list (Seq.cast ps) |> //  appending those from paperspace

        List.map extractText |>      // Extract the text from each object

        sortedWords |>               // Get a sorted, canonical list of words

        printList                    // Print the resultant words

      // As usual, committing is cheaper than aborting

      tr.Commit()

    Introducing parallel processing in AutoCAD via F# Asynchronous Workflows

    As mentioned previously, pure functional code lends itself to be run on multiple computing cores in parallel. While the tools aren’t yet there to make this happen automatically – via implicit parallelization – this is a likely outcome, over the coming years. For now we have the possibility of writing code that uses explicit parallelization – where we specify the tasks we know can be executed at the same time and leave the language and runtime to take care of the coordination.

    There are a couple of ways to do this, right now: the Parallel Extensions to .NET (also in CTP stage and due for inclusion in Visual Studio 2010) provide a number of parallel constructs, such as parallel versions of for and while loops. F# currently provides the capability to define and execute Asynchronous Workflows, which is what we’re going to look at now.

    First, let’s take a look at a sample application that we’re going to parallelize. This sample goes through and queries, via RSS, the latest posts on a number of different blogs. It then generates AutoCAD geometry – text with a hyperlink – for each of these posts. So we turn AutoCAD into an RSS reader, for all intents and purposes.

        1 // Use lightweight F# syntax

        2

        3 #light

        4

        5 // Declare a specific namespace and module name

        6

        7 module MyNamespace.MyApplication

        8

        9 // Import managed assemblies

       10

       11 open Autodesk.AutoCAD.Runtime

       12 open Autodesk.AutoCAD.ApplicationServices

       13 open Autodesk.AutoCAD.DatabaseServices

       14 open Autodesk.AutoCAD.Geometry

       15 open System.Xml

       16 open System.IO

       17 open System.Net

       18

       19 // The RSS feeds we wish to get. The first two values are

       20 // only used if our code is not able to parse the feed's XML

       21

       22 let feeds =

       23   [ ("Through the Interface",

       24     "http://blogs.autodesk.com/through-the-interface",

       25     "http://through-the-interface.typepad.com/through_the_interface/atom.xml");

       26

       27     ("Don Syme's F# blog",

       28     "http://blogs.msdn.com/dsyme/",

       29     "http://blogs.msdn.com/dsyme/rss.xml");

       30

       31     ("Shaan Hurley's Between the Lines",

       32     "http://autodesk.blogs.com/between_the_lines",

       33     "http://autodesk.blogs.com/between_the_lines/rss.xml");

       34

       35     ("Scott Sheppard's It's Alive in the Lab",

       36     "http://blogs.autodesk.com/labs",

       37     "http://labs.blogs.com/its_alive_in_the_lab/rss.xml");

       38

       39     ("Volker Joseph's Beyond the Paper",

       40     "http://blogs.autodesk.com/beyond_the_paper",

       41     "http://dwf.blogs.com/beyond_the_paper/atom.xml") ]

       42

       43 // Fetch the contents of a web page, synchronously

       44

       45 let httpSync (url:string) =

       46   let req = WebRequest.Create(url)

       47   use resp = req.GetResponse()

       48   use stream = resp.GetResponseStream()

       49   use reader = new StreamReader(stream)

       50   reader.ReadToEnd()

       51

       52 // Load an RSS feed's contents into an XML document object

       53 // and use it to extract the titles and their links

       54 // Hopefully these always match (this could be coded more

       55 // defensively)

       56

       57 let titlesAndLinks (name, url, xml) =

       58   try

       59     let xdoc = new XmlDocument()

       60     xdoc.LoadXml(xml)

       61

       62     let titles =

       63       [ for n in xdoc.SelectNodes("//*[name()='title']")

       64           -> n.InnerText ]

       65     let links =

       66       [ for n in xdoc.SelectNodes("//*[name()='link']") ->

       67           let inn = n.InnerText

       68           if  inn.Length > 0 then

       69             inn

       70           else

       71             let href = n.Attributes.GetNamedItem("href").Value

       72             let rel = n.Attributes.GetNamedItem("rel").Value

       73             if href.Contains("feedburner") or rel.Contains("enclosure") then

       74                 ""

       75             else

       76               href ]

       77

       78     let descs =

       79       [ for n in xdoc.SelectNodes

       80           ("//*[name()='description' or name()='subtitle' or name()='summary']")

       81             -> n.InnerText ]

       82

       83     // A local function to filter out duplicate entries in

       84     // a list, maintaining their current order.

       85     // Another way would be to use:

       86     //    Set.of_list lst |> Set.to_list

       87     // but that results in a sorted (probably reordered) list.

       88

       89     let rec nub lst =

       90       match lst with

       91       | a::[] -> [a]

       92       | a::b ->

       93         if a = List.hd b then

       94           nub b

       95         else

       96           a::nub b

       97       | [] -> []

       98

       99     // Filter the links to get (hopefully) the same number

      100     // and order as the titles and descriptions

      101

      102     let real = List.filter (fun (x:string) -> x.Length > 0) 

      103     let lnks = real links |> nub

      104

      105     // Return a link to the overall blog, if we don't have

      106     // the same numbers of titles, links and descriptions

      107

      108     let lnum = List.length lnks

      109     let tnum = List.length titles

      110     let dnum = List.length descs

      111

      112     if tnum = 0 || lnum = 0 || lnum <> tnum || dnum <> tnum then

      113       [(name,url,url)]

      114     else

      115       List.zip3 titles lnks descs

      116   with _ -> []

      117

      118 // For a particular (name,url) pair,

      119 // create an AutoCAD HyperLink object

      120

      121 let hyperlink (name,url,desc) =

      122   let hl = new HyperLink()

      123   hl.Name <- url

      124   hl.Description <- desc

      125   (name, hl)

      126

      127 // Download an RSS feed and return AutoCAD HyperLinks for its posts

      128

      129 let hyperlinksSync (name, url, feed) =

      130   let xml = httpSync feed

      131   let tl = titlesAndLinks (name, url, xml)

      132   List.map hyperlink tl

      133

      134 // Now we declare our command

      135

      136 [<CommandMethod("rss")>]

      137 let createHyperlinksFromRss() =

      138

      139   let starttime = System.DateTime.Now

      140

      141   // Let's get the usual helpful AutoCAD objects

      142

      143   let doc =

      144     Application.DocumentManager.MdiActiveDocument

      145   let ed = doc.Editor

      146   let db = doc.Database

      147

      148   // "use" has the same effect as "using" in C#

      149

      150   use tr =

      151     db.TransactionManager.StartTransaction()

      152

      153   // Get appropriately-typed BlockTable and BTRs

      154

      155   let bt =

      156     tr.GetObject

      157       (db.BlockTableId,OpenMode.ForRead)

      158     :?> BlockTable

      159   let ms =

      160     tr.GetObject

      161       (bt.[BlockTableRecord.ModelSpace],

      162        OpenMode.ForWrite)

      163     :?> BlockTableRecord

      164

      165   // Add text objects linking to the provided list of

      166   // HyperLinks, starting at the specified location

      167

      168   // Note the valid use of tr and ms, as they are in scope

      169

      170   let addTextObjects (pt : Point3d) lst =

      171     // Use a for loop, as we care about the index to

      172     // position the various text items

      173

      174     let len = List.length lst

      175     for index = 0 to len - 1 do

      176       let txt = new DBText()

      177       let (name:string,hl:HyperLink) = List.nth lst index

      178       txt.TextString <- name

      179       let offset =

      180         if index = 0 then

      181           0.0

      182         else

      183           1.0

      184

      185       // This is where you can adjust:

      186       //  the initial outdent (x value)

      187       //  and the line spacing (y value)

      188

      189       let vec =

      190         new Vector3d

      191           (1.0 * offset,

      192            -0.5 * (Int32.to_float index),

      193            0.0)

      194       let pt2 = pt + vec

      195       txt.Position <- pt2

      196       ms.AppendEntity(txt) |> ignore

      197       tr.AddNewlyCreatedDBObject(txt,true)

      198       txt.Hyperlinks.Add(hl) |> ignore

      199

      200   // Here's where we use the varous functions

      201   // we've defined

      202

      203   let links =

      204     List.map hyperlinksSync feeds

      205

      206   // Add the resulting objects to the model-space 

      207

      208   let len = List.length links

      209   for index = 0 to len - 1 do

      210

      211     // This is where you can adjust:

      212     //  the column spacing (x value)

      213     //  the vertical offset from origin (y axis)

      214

      215     let pt =

      216       new Point3d

      217         (15.0 * (Int32.to_float index),

      218         30.0,

      219         0.0)

      220     addTextObjects pt (List.nth links index)

      221

      222   tr.Commit()

      223

      224   let elapsed =

      225       System.DateTime.op_Subtraction(System.DateTime.Now, starttime)

      226

      227   ed.WriteMessage("\nElapsed time: " + elapsed.ToString())

    I have numbered the lines, to make it easier for us to talk about the changes that are needed to introduce parallelism into this sample. Both synchronous and asynchronous versions of this application are available on my blog.

    I won’t go through the above code in detail, here: firstly, it’s not intended as a perfect implementation of an RSS consumer – there are too many variations in the way RSS is implemented by different sites, so I know for a fact that this code will not work for certain blogs – it’s really intended to be an example of a – potentially time-consuming – asynchronous (in this case network-based) activity that is easy to run in parallel.

    A word of caution: AutoCAD is not thread-safe – it is very much a single-threaded application – so we need to coordinate the results of these tasks prior to making the changes to the AutoCAD database. Luckily F# makes this very easy for us to do, so that’s really not a problem.

    Here is the updated source that makes use of Asynchronous Workflows, with the modified/new lines highlighted in red (with a grey background for those reading this in black & white :-):

        1 // Use lightweight F# syntax

        2

        3 #light

        4

        5 // Declare a specific namespace and module name

        6

        7 module MyNamespace.MyApplicationAsync

        8

        9 // Import managed assemblies

       10

       11 open Autodesk.AutoCAD.Runtime

       12 open Autodesk.AutoCAD.ApplicationServices

       13 open Autodesk.AutoCAD.DatabaseServices

       14 open Autodesk.AutoCAD.Geometry

       15 open System.Xml

       16 open System.IO

       17 open System.Net

       18

       19 // The RSS feeds we wish to get. The first two values are

       20 // only used if our code is not able to parse the feed's XML

       21

       22 let feeds =

       23   [ ("Through the Interface",

       24     "http://blogs.autodesk.com/through-the-interface",

       25     "http://through-the-interface.typepad.com/through_the_interface/atom.xml");

       26

       27     ("Don Syme's F# blog",

       28     "http://blogs.msdn.com/dsyme/",

       29     "http://blogs.msdn.com/dsyme/rss.xml");

       30

       31     ("Shaan Hurley's Between the Lines",

       32     "http://autodesk.blogs.com/between_the_lines",

       33     "http://autodesk.blogs.com/between_the_lines/rss.xml");

       34

       35     ("Scott Sheppard's It's Alive in the Lab",

       36     "http://blogs.autodesk.com/labs",

       37     "http://labs.blogs.com/its_alive_in_the_lab/rss.xml");

       38

       39     ("Volker Joseph's Beyond the Paper",

       40     "http://blogs.autodesk.com/beyond_the_paper",

       41     "http://dwf.blogs.com/beyond_the_paper/atom.xml") ]

       42

       43 // Fetch the contents of a web page, asynchronously

       44

       45 let httpAsync(url:string) =

       46   async { let req = WebRequest.Create(url)

       47           use! resp = req.GetResponseAsync()

       48           use stream = resp.GetResponseStream()

       49           use reader = new StreamReader(stream)

       50           return reader.ReadToEnd() }

       51

       52 // Load an RSS feed's contents into an XML document object

       53 // and use it to extract the titles and their links

       54 // Hopefully these always match (this could be coded more

       55 // defensively)

       56

       57 let titlesAndLinks (name, url, xml) =

       58   try

       59     let xdoc = new XmlDocument()

       60     xdoc.LoadXml(xml)

       61

       62     let titles =

       63       [ for n in xdoc.SelectNodes("//*[name()='title']")

       64           -> n.InnerText ]

       65     let links =

       66       [ for n in xdoc.SelectNodes("//*[name()='link']") ->

       67           let inn = n.InnerText

       68           if  inn.Length > 0 then

       69             inn

       70           else

       71             let href = n.Attributes.GetNamedItem("href").Value

       72             let rel = n.Attributes.GetNamedItem("rel").Value

       73             if href.Contains("feedburner") or rel.Contains("enclosure") then

       74                 ""

       75             else

       76               href ]

       77

       78     let descs =

       79       [ for n in xdoc.SelectNodes

       80           ("//*[name()='description' or name()='subtitle' or name()='summary']")

       81             -> n.InnerText ]

       82

       83     // A local function to filter out duplicate entries in

       84     // a list, maintaining their current order.

       85     // Another way would be to use:

       86     //    Set.of_list lst |> Set.to_list

       87     // but that results in a sorted (probably reordered) list.

       88

       89     let rec nub lst =

       90       match lst with

       91       | a::[] -> [a]

       92       | a::b ->

       93         if a = List.hd b then

       94           nub b

       95         else

       96           a::nub b

       97       | [] -> []

       98

       99     // Filter the links to get (hopefully) the same number

      100     // and order as the titles and descriptions

      101

      102     let real = List.filter (fun (x:string) -> x.Length > 0) 

      103     let lnks = real links |> nub

      104

      105     // Return a link to the overall blog, if we don't have

      106     // the same numbers of titles, links and descriptions

      107

      108     let lnum = List.length lnks

      109     let tnum = List.length titles

      110     let dnum = List.length descs

      111

      112     if tnum = 0 || lnum = 0 || lnum <> tnum || dnum <> tnum then

      113       [(name,url,url)]

      114     else

      115       List.zip3 titles lnks descs

      116   with _ -> []

      117

      118 // For a particular (name,url) pair,

      119 // create an AutoCAD HyperLink object

      120

      121 let hyperlink (name,url,desc) =

      122   let hl = new HyperLink()

      123   hl.Name <- url

      124   hl.Description <- desc

      125   (name, hl)

      126

      127 // Use asynchronous workflows in F# to download

      128 // an RSS feed and return AutoCAD HyperLinks

      129 // corresponding to its posts

      130

      131 let hyperlinksAsync (name, url, feed) =

      132   async { let! xml = httpAsync feed

      133           let tl = titlesAndLinks (name, url, xml)

      134           return List.map hyperlink tl }

      135

      136 // Now we declare our command

      137

      138 [<CommandMethod("arss")>]

      139 let createHyperlinksFromRssAsync() =

      140

      141   let starttime = System.DateTime.Now

      142

      143   // Let's get the usual helpful AutoCAD objects

      144

      145   let doc =

      146     Application.DocumentManager.MdiActiveDocument

      147   let ed = doc.Editor

      148   let db = doc.Database

      149

      150   // "use" has the same effect as "using" in C#

      151

      152   use tr =

      153     db.TransactionManager.StartTransaction()

      154

      155   // Get appropriately-typed BlockTable and BTRs

      156

      157   let bt =

      158     tr.GetObject

      159       (db.BlockTableId,OpenMode.ForRead)

      160     :?> BlockTable

      161   let ms =

      162     tr.GetObject

      163       (bt.[BlockTableRecord.ModelSpace],

      164        OpenMode.ForWrite)

      165     :?> BlockTableRecord

      166

      167   // Add text objects linking to the provided list of

      168   // HyperLinks, starting at the specified location

      169

      170   // Note the valid use of tr and ms, as they are in scope

      171

      172   let addTextObjects (pt : Point3d) lst =

      173     // Use a for loop, as we care about the index to

      174     // position the various text items

      175

      176     let len = List.length lst

      177     for index = 0 to len - 1 do

      178       let txt = new DBText()

      179       let (name:string,hl:HyperLink) = List.nth lst index

      180       txt.TextString <- name

      181       let offset =

      182         if index = 0 then

      183           0.0

      184         else

      185           1.0

      186

      187       // This is where you can adjust:

      188       //  the initial outdent (x value)

      189       //  and the line spacing (y value)

      190

      191       let vec =

      192         new Vector3d

      193           (1.0 * offset,

      194            -0.5 * (Int32.to_float index),

      195            0.0)

      196       let pt2 = pt + vec

      197       txt.Position <- pt2

      198       ms.AppendEntity(txt) |> ignore

      199       tr.AddNewlyCreatedDBObject(txt,true)

      200       txt.Hyperlinks.Add(hl) |> ignore

      201

      202   // Here's where we do the real work, by firing

      203   // off - and coordinating - asynchronous tasks

      204   // to create HyperLink objects for all our posts

      205

      206   let links =

      207     Async.Run

      208       (Async.Parallel

      209         [ for (name,url,feed) in feeds ->

      210           hyperlinksAsync (name,url,feed) ])

      211     |> Array.to_list

      212

      213   // Add the resulting objects to the model-space 

      214

      215   let len = List.length links

      216   for index = 0 to len - 1 do

      217

      218     // This is where you can adjust:

      219     //  the column spacing (x value)

      220     //  the vertical offset from origin (y axis)

      221

      222     let pt =

      223       new Point3d

      224         (15.0 * (Int32.to_float index),

      225         30.0,

      226         0.0)

      227     addTextObjects pt (List.nth links index)

      228

      229   tr.Commit()

      230

      231   let elapsed =

      232       System.DateTime.op_Subtraction(System.DateTime.Now, starttime)

      233

      234   ed.WriteMessage("\nElapsed time: " + elapsed.ToString())

    Let's look at the specific changes:

    • Line 7 has been changed to allow both files to be part of the same project.
    • Lines 45-50 implement a new, asynchronous function to download content from a URL. The async primitive coordinates a set of activities, while the let! and use! statements indicate that these right-hand side of the operation will be run asynchronously and that the results should be bound to the left. So here we're only getting the HTTP content asynchronously - the reading is to occur synchronously.
    • Lines 131-134 implement an asynchronous task that not only calls our asynchronous HTTP request function but coordinates the creation of AutoCAD geometry based on the contents received.
    • Lines 207-211 are where we make use of these newly-defined functions by firing them off in parallel (the framework will use the processing capabilities available to it to execute the tasks as efficiently as possible) and coordinating the results into a single array, which we convert to a list to maintain our previous processing code.

    When we run either the RSS or ARSS (its asynchronous version), we should see this kind of result:

    RSS feeds inside AutoCAD

    Figure 11 – AutoCAD geometry created from our RSS feeds

    Now let’s see how they compare in terms of performance. I executed the RSS and ARSS commands a number of times in sequence to get a feel for relative performance.

    Command: rss

    Elapsed time: 00:00:08.1958195

    Command: arss

    Elapsed time: 00:00:02.2802280

    Command: rss

    Elapsed time: 00:00:04.1264126

    Command: arss

    Elapsed time: 00:00:03.6343634

    Command: rss

    Elapsed time: 00:00:03.6563656

    Command: arss

    Elapsed time: 00:00:01.9891989

    Command: rss

    Elapsed time: 00:00:03.1673167

    Command: arss

    Elapsed time: 00:00:03.1223122

    Command: rss

    Elapsed time: 00:00:05.7375737

    Command: arss

    Elapsed time: 00:00:01.9391939

    The first execution time is much higher due to an initial startup penalty or the need to fill some page cache with the content. On average, though, the asynchronous code runs in 60-70% of the time needed by the synchronous version. The code was run a dual-core notebook: some of the performance will be related to using both cores, but most will be due to the parallelization of asynchronous tasks that have some latency due to use of the network. With more accesses in parallel you would see this performance difference become increasingly exaggerated.

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e2010535ba8716970c

    Listed below are links to weblogs that reference AU Handouts: AutoCAD® .NET - Developing for AutoCAD® Using F# - Part 2:

    Comments

    Hi,
    Dear want to do certification course in AutoCAD.Net. any one can help me. where can i do AutoCAD.Net.

    Shadab -

    There's no official certification for AutoCAD .NET. My team delivers Autodesk's API training and does provide an AutoCAD .NET training.

    Looking at your profile it seems you're based in India. The next class we're holding there (according to the schedule) is April 9th & 10th, 2009, in Bangalore.

    The class is based on the AutoCAD .NET Labs which is downloadable from the AutoCAD Developer Center, in case you'd rather study offline.

    Regards,

    Kean

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search