VB.Net Use XmlNodeList in a parallel ForEach

732 views Asked by At

I have a piece of code that iterates over the nodes in an XmlNodeList and creates a different object for each one depending on the node name and adds it to a list for printing.

For Each node as XmlNode In nodeList
    Select Case node.Name.ToUpper()
        Case "SHAPE"
            _items.Add(New ShapeTemplate(node, Me))
        Case "TEXTBLOCK"
            _items.Add(New TextblockTemplate(node, Me))
    End Select
Next

This code works fine, but because of all the work that has to be done by the ShapeTemplate and TextblockTemplate constructors, it is VERY slow. Since the order objects are added to _items doesn't matter, I thought a good way to speed it up would be to use a parallel.ForEach loop. The problem is XmlNodeList can't be used with parallel.ForEach because it is a non-generic collection. I've been looking into ways to convert XmlNodeList to List(Of XmlNode) with no luck. The answer I keep seeing come up is

Dim nodes as New List(Of xmlNode)(nodeList.Cast(Of xmlNode)())

But when I try it, I get an error telling me that 'Cast' is not a member of XmlNodeList.

I've also tried using TryCast like this

Dim nodes as List(Of XmlNode) = TryCast(CObj(nodeList), List(Of XmlNode))

but it results in nodes being Nothing because the object can't be cast.

Does anyone know how I can use XmlNodeList in a parallel.ForEach loop?

EDIT: I'm trying to avoid using a loop for the conversion if I can

2

There are 2 answers

0
Mark On BEST ANSWER

You could use Parallel LINQ instead of Parallel.ForEach, which seems like a more natural fit for this sort of transformation. This looks just like a normal LINQ query, but with .AsParallel() added.

Imports System.Linq

' _items will not be in same order as nodes in nodeList.
' Add .AsOrdered() after .AsParallel() to maintain order, if needed.
_items = (
    From node In nodeList.Cast(Of XmlNode)().AsParallel()
    Select item = CreateTemplate(node)
    Where item IsNot Nothing
).ToList()

Function CreateTemplate(node As XmlNode) As ITemplate ' interface or base class for your types
    Dim item As ITemplate
    Select Case node.Name.ToUpper()
        Case "SHAPE"
            item = New ShapeTemplate(node, Me)
        Case "TEXTBLOCK"
            item = New TextblockTemplate(node, Me)
        Case Else
            item = Nothing
    End Select
    Return item
End Function
2
djv On

As seen here, the XmlNodeList can be converted to a generic List(Of XmlNode) by passing it to the constructor with OfType.

' I added so your code could compile.
' I assumed an interface shared by ShapeTemplate and TextblockTemplate
Dim nodeList As XmlNodeList
Dim _items As New List(Of ITemplate)

Dim _nodes As New List(Of XmlNode)(nodeList.OfType(Of XmlNode))

Now the parallel loop. Note, if you are adding to a non-threadsafe collection such as List, you will need to synchronize adding the objects to the list. So i separated the time consuming portion (Template constructor) from the fast operation (adding to the list). This should improve your performance.

Dim oLock As New Object
Parallel.ForEach(
    _nodes,
    Sub(node)
        Dim item As ITemplate
        Select Case node.Name.ToUpper()
            Case "SHAPE"
                item = New ShapeTemplate(node, Me)
            Case "TEXTBLOCK"
                item = New TextblockTemplate(node, Me)
            Case Else
                item = Nothing ' or, do something else?
        End Select
        SyncLock oLock
            _items.Add(item)
        End SyncLock
    End Sub)