Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / .NET / .NET4.5

A new approach to multithreading?

3.80/5 (3 votes)
3 Sep 2014CPOL2 min read 13.2K  
A strange idea I had for doing multithreading more managable.

Introduction

We have to admit, multithreading is hard to understand. On the other hand, if you want to do more in less time, MT is the way to go. I would like to present a concept/pattern I developed to make MT easier in some scenarios. 

Background

Under the introduction to CQRS in our company I wanted to fill all dependent properties on a DTO in parallel.

Using the Code

The 'old way' of doing this in a sequential manner would be something like this, very simplified of course

VB.NET
Public Class Person
    Public Property Id As Integer
    Public Property Name As String

    Public Property Email As IEnumerable(Of EMail)
    Public Property Address As IEnumerable(Of Address)
End Class

Public Class EMail
    Public Property Id As Integer
    Public Property PersonId As Integer
    Public Property Adress As String
End Class
Public Class Address
    Public Property Id As Integer
    Public Property PersonId As Integer
    Public Property Address As String
End Class
VB.NET
Public Class GetPersonInfo

       Public ConnectionInfo As New ServerConnectionInfo With {.Address = "sql", .Database = "person", .UserName = "sa", .Password = "*******"} 'Don't worry about this..

       Public shared Sub FillPerson(id as integer,byref p as person)
           Dim cmd As New CommandInfo
           cmd.Parameters.Add("Id", DbType.Int32, Query.Id)
           cmd.CommandText = "Select * from Person where Id = @Id "
           Data.Exec(ConnectionInfo, cmd, Result)
       End Sub

       Public Shared Sub FillEmail(id as integer,byref l as iEnumerable(of Email))
           Dim list As New List(Of EMail)
           Dim cmd As New CommandInfo
           cmd.Parameters.Add("Id", DbType.Int32, Query.Id)
           cmd.CommandText = "Select * from HrElectronicAddress where unitId = @Id "
           Data.Exec(ConnectionInfo, cmd, list)
           l = list
       End Sub

       Public Shared Sub FillAddress()
           Dim list As New List(Of Addresss)
           Dim cmd As New CommandInfo
           cmd.Parameters.Add("Id", DbType.Int32, Query.Id)
           cmd.CommandText = "Select * from HrAddress where unitId = @Id "
           Data.Exec(ConnectionInfo, cmd, list)
           l = list
       End Sub

   End Class
VB.NET
'To use this
Dim p as new Person
dim Id as integer = 1
GetPersonInfo.FillPerson(Id,p))
GetPersonInfo.FillEmail(Id,p.Email))
GetPersonInfo.FillAddress(Id,p.Address))

My goal was to re-use much of the same logic for filling, but be able to do it in parallel. 

First of all, we have to do some wrapping of the functions.

This is just an Interface and a baseclass to be able to identify the multithreaded queryhandlers. 

VB.NET
    Public Interface IParalellQuery
        ReadOnly Property InnerResult As Object
        Property InnerQuery As Object
    End Interface

Public MustInherit Class ParalellQueryBase(Of TQuery As IAmAQuery, TResult As New)
        Implements IParalellQuery

        Public Query As TQuery
        Public Result As TResult

        Public Sub New()
            Result = New TResult
        End Sub

        Friend Property InnerQuery As Object Implements IParalellQuery.InnerQuery
            Set(value As Object)
                Query = CType(value, TQuery)
            End Set
            Get
                Return Query
            End Get
        End Property

        Public ReadOnly Property InnerResult As Object Implements IParalellQuery.InnerResult
            Get
                Return Result
            End Get
        End Property
    End Class
VB.NET
Public Class GetPersonInfoQuery
        Inherits QueryBase

        Public Property Id As Integer

        Public Overrides ReadOnly Property Name As String
            Get
                Return "GetPersonInfo"
            End Get
        End Property
    End Class

The same as above but prepared for multithreading..

VB.NET
Public Class GetPersonInfoQueryHandler
       Inherits ParalellQueryBase(Of GetPersonInfoQuery, Person)
       Public ConnectionInfo As New ServerConnectionInfo With {.Address = "sql", .Database = "person", .UserName = "sa", .Password = "*******"} 'Don't worry about this.. 

        Public Sub FillPerson()
            Dim cmd As New CommandInfo
            cmd.Parameters.Add("Id", DbType.Int32, Query.Id)
            cmd.CommandText = "Select * from Person where Id = @Id "
            Data.Exec(ConnectionInfo, cmd, Result)
        End Sub

        Public Sub FillEmail()
            Dim list As New List(Of EMail)
            Dim cmd As New CommandInfo
            cmd.Parameters.Add("Id", DbType.Int32, Query.Id)
            cmd.CommandText = "Select * from HrElectronicAddress where unitId = @Id "
            Data.Exec(ConnectionInfo, cmd, list)
            Result.Email = list
        End Sub

        Public Sub FillAddress()
            Dim list As New List(Of Addresss)
            Dim cmd As New CommandInfo
            cmd.Parameters.Add("Id", DbType.Int32, Query.Id)
            cmd.CommandText = "Select * from HrAddress where unitId = @Id "
            Data.Exec(ConnectionInfo, cmd, list)
            Result.Addresss = list
        End Sub

    End Class

The Result property used, is from the baseclass, and is of the correct type thanks to the generic type parameter. 

The parameters needed inside the Sub's is passed through the GetPersoninfoQuery class. In our case, containing just the Id of the person. 

How to invoke this?

VB.NET
Dim res As Person
Dim q As New GetPersonInfoQuery

    q.Id = 1
    res = q.Execute(Of Person)()

Here is the main code for calling the subs on the handler. 

VB.NET
If MultiHandlers.ContainsKey(q.GetType) Then
     Dim handler = MultiHandlers(q.GetType)
     Dim target As Query.IParalellQuery = CType(handler.CreateInstance, IParalellQuery)
     target.InnerQuery = q

     handler.Methods.AsParallel.ForAll(Sub(m)
                                           m.Invoke(target, {})
                                       End Sub)
     Return target.InnerResult
 End If

Some explanations needed here. 

All classes implementing the IMultiquery interface is found in the application and there the system builds and index to know which class handles which Query. The target her is an instace of the GetPersonInfoQueryHandler.

Alle public sub's on the handler type is extracted using reflection and inserted into the Methods property of the handler, then using the AsParallel.ForAll extension method to invoke all sub on the target.

Points of Interest

Did this actually improve anything? Well the speed is better.

VB.NET
<Test> Public Sub SpeedTest()
        Dim tinfo As New Utils.TimingInfos
        Dim res As Person
        Dim q As New GetPersonInfoQuery
        q.Id = 1

        res = CType(Query.Handling.ExecuteQuery(q), Person)
        
        Using New Utils.InlineTimer("Paralell", tinfo)
            For x = 0 To 100
                res = CType(Query.Handling.ExecuteQuery(q), Person)
            Next
        End Using
        Using New Utils.InlineTimer("Seq", tinfo)
            For x = 0 To 100
                Dim t As New GetPersonInfoQueryHandler
                t.Query = q
                res = t.Result
                t.FillEmail()
                t.FillPerson()
                t.FillAddress()
            Next
        End Using

        Debug.Print(tinfo("Paralell").List.Sum().ToString) 'Took:548449 ticks
        Debug.Print(tinfo("Seq").List.Sum().ToString)      'Took:731012 ticks

    End Sub

There is of course some time overhead when doing this, but the overhead is eaten up when in my case 3 sub/functions is called. The timesavings will be greater the longer each sub takes.

I belive this approach is a way to abstract some of the difficulties of multithreading in some given scenarios. 

Any comments to this approach is very welcome.  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)