none
Threads in CLR

    Question

  • I have a CLR table function that works fine but trying to improve its performance by using some threading.

    I have the following code working but my WaitAll doesn't work when it is compiled and ran from SQL, sometimes it returns all of the data from all of the threads and sometimes I only get part of the records.

    Any ideas on how I can be sure all of the threads have returned their rows before I go on? The GetBOMListingNew returns a list of parts which is making SQL calls, etc. If I run it in the debugger, after the Task.WaitAll, I can see some of the data updating in my debugging variables...so it works, it is just that I can't figure out how to be sure all of the threads are finished. 

    I have done all of the settings for the Unsafe permissions, etc.


                For i As Int32 = 0 To n
                    Dim ChildConnection = New SqlConnection(_inputOptions.IntegratedSecurityConnectionString)
                    ConnectionArray(i) = ChildConnection
                    ChildConnection.Open()
                    objBomid = objBomidList(i)
                    TaskPartsArray(i) = Task(Of List(Of Part)).Factory.StartNew(Function() BOMThread.GetBomListingNEW(objPart _
                                      , objBomid _
                                      , strRanking _
                                      , strOriginalRanking _
                                      , objPart.DeltaXOutput _
                                      , objPart.DeltaYOutput _
                                      , objPart.DeltaZOutput _
                                      , _inputOptions _
                                      , _lineGroupItemOptionsList _
                                      , _partPriceAggregateList _
                                      , _autoRuleList _
                                      , _partAttributeList _
                                      , _partAttributeMethodList _
                                      , _lineGroupItemOptionRankingList _
                                      , _completePartList _
                                      , _processStepsList _
                                      , _spList _
                                      , ChildConnection _
                                      ) _
                        )
                    TaskList(i) = TaskPartsArray(i)

                Next

                Try
                    Task.WaitAll(TaskList)
                    'Thread.Sleep(500)
                    For ii As Int32 = 0 To n
                            TaskPartsList = TaskPartsArray(ii).Result
                            objBomList.AddRange(TaskPartsList)
                    Next

                    For ii As Int32 = 0 To n
                        ConnectionArray(ii).Close()
                    Next
                Catch ex As Exception
                    Dim i As Integer
                    Dim strErr As String = ex.Message
                    Dim objPartErr As Part
                    objPartErr = AddPartAsSqlErrorMsg(strErr, _inputOptions)
                    objBomList.Add(objPartErr)
                    i = 0
                End Try

    • Moved by Tom Phillips Tuesday, December 03, 2013 5:03 PM CLR coding question
    Monday, December 02, 2013 2:35 PM

Answers

  • The mantra for threaded programming is to use immutable objects.  Any time you have a global or captured object that can be mutated, you introduce the need for expensive and error-prone synchronization between your threads.

    Just ensure that each object is either immutable, or local in scope.  Then you don't have to worry about synchronization.  Do not have any global collections that multiple threads access.  Just pass read-only data to each function and accumulate all the results in your outer method.

    For you, that means passing each task read-only objects, and have it return data, not copy it to a shared list. 

    So ensure that BOMThread.GetBomListingNEW does not modify any of its arguments or any global (shared) objects.

    David


    David http://blogs.msdn.com/b/dbrowne/

    • Marked as answer by Jeff573 Wednesday, February 12, 2014 7:12 PM
    Wednesday, February 12, 2014 3:19 PM

All replies

  • If I go in and delete all of the connections that were created from the CLR and re-run the CLR I get all of my records...is it an issue with it somehow re-using the sql connections that are already there? if so, what is best way to get rid of the connections? I think I put some code in to close() it but that didn't seem to work.

    Monday, December 02, 2013 2:44 PM
  • Hi Jeff573,
     
    Thank you for your question. 
     
    I am trying to involve someone more familiar with this topic for a further look at this issue. Sometime delay might be expected from the job transferring. Your patience is greatly appreciated. 
     
    Thank you for your understanding and support.
     
    Best Regards,
    Allen Li

    If you have any feedback on our support, please click here.


    Allen Li
    TechNet Community Support

    Tuesday, December 03, 2013 7:17 AM
    Moderator
  • Judging from your second post, there is a whole lot of code that we are not seeing. It could help if you post the lot.


    Erland Sommarskog, SQL Server MVP, esquel@sommarskog.se
    Tuesday, December 03, 2013 8:06 AM
  • Yes there is a lot of code I am not showing. The GetBomListingNew function takes in a list of parameters (last one being the sqlconnection). This function can be called recursively.  This clr is used to build a list of parts for a given BOM (Bill of Material).  So for example if you start with BOMID=1000, it may return 1000.100, 1000.101, 1000.102, 1000.102.1, 1000.102.2, etc.
    The way it works, within the GetBOMListingNew function I call other functions such as GetBOMIdData with is a function that returns a Part.  There are other functions that get other properties for a part, etc.  but the basic coding is pretty much the same as this function below. I pass in as one of the parameters the _threadConn which is my impersonation sqlconnection since this is to be called from a thread.

    There is many functions and I am afraid it would be way too much code to send you unless I zipped it up and sent it, etc.

    Let me know if you need more code or more info. 

    The way I have structured this (trying to use threads) is I have a GetBOMListingStart function that I only call one time. It is listed below also.  It starts the call to the GetBomListingNew which then can be called recursively from itself.

       Public Shared Function GetBomidData(ByVal _bomid As SqlInt64 _
                                          , ByVal _strRanking As String _
                                          , ByVal _strOriginalRanking As String _
                                          , ByVal _inputOptions As Input _
                                          , ByVal _lineGroupItemOptionRankingList As LineGroupItemOptionRankingList _
                                          , ByVal _processStepsList As ProcessStepList _
                                          , ByVal _spList As Dictionary(Of String, List(Of Dictionary(Of String, Object))) _
                                          , ByVal _threadConn As SqlConnection _
                                          ) As Part


            Dim strSubroutineName As String = String.Empty
            Dim objLineGroupItemOptionRanking As LineGroupItemOptionRanking
            Dim objPart As Part = Nothing
            Dim objSpCommandList As List(Of Dictionary(Of String, Object)) = Nothing
            Dim strLineGroupItemOptionList As SqlString
            Dim strSpCommand As String
            Dim strStoredProcedure As String

            objLineGroupItemOptionRanking = _lineGroupItemOptionRankingList.FindByRanking(_strRanking)

            If Not objLineGroupItemOptionRanking Is Nothing Then
                strLineGroupItemOptionList = objLineGroupItemOptionRanking.LineGroupItemOptions
            Else
                strLineGroupItemOptionList = ""
            End If

            strStoredProcedure = "CabinetDesigner.WWCabinetCLR_BOMValue_Select"

            strSpCommand = String.Format("{0} {1}, {2}, '{3}'" _
                                         , strStoredProcedure _
                                         , _inputOptions.DivisionId _
                                         , _bomid.ToSqlString _
                                         , _inputOptions.CheckDate.ToString() _
                                         )
            'For Debugging
            If _inputOptions.DebugFlag = 2 Then
                strSubroutineName = MethodBase.GetCurrentMethod.Name
                AddProcessStep(strSubroutineName _
                               , _bomid _
                               , _strRanking _
                               , "Get Part from DB" _
                               , strSpCommand _
                               , "Get Part from DB" _
                               , Nothing _
                               , _processStepsList _
                               )
            End If

            'see if sp has already been used.
            objSpCommandList = FindSp(_spList _
                                      , strSpCommand _
                                      )

            If objSpCommandList Is Nothing Then

                Try

                    'not found, run the sp
                    '_inputOptions.IntegratedContextConnection.Open()
                    'Dim cmd As SqlCommand = _inputOptions.IntegratedContextConnection.CreateCommand()
                    Dim cmd As SqlCommand = _threadConn.CreateCommand()
                    'Dim cmd As SqlCommand = _inputOptions.ContextConnection.CreateCommand()
                    cmd.CommandType = CommandType.StoredProcedure
                    cmd.CommandText = strStoredProcedure
                    cmd.Parameters.Add(New SqlParameter("@DivisionID", _inputOptions.DivisionId))
                    cmd.Parameters.Add(New SqlParameter("@BOMID", _bomid))
                    cmd.Parameters.Add(New SqlParameter("@CheckDate", _inputOptions.CheckDate))

                    'debug here {strSpCommand}
                    Using rdr As SqlDataReader = cmd.ExecuteReader()

                        If rdr.Read Then

                            objPart = AddPartFromSqlDataReader(rdr _
                                                               , _strRanking _
                                                               , _strOriginalRanking _
                                                               , strLineGroupItemOptionList _
                                                               , _inputOptions _
                                                               )


                            ''Add sp to collection
                            AddSp(rdr _
                                  , strSpCommand _
                                  , _spList _
                                  )

                        End If

                    End Using

                Catch ex As Exception
                    Dim strErr As String
                    strSubroutineName = MethodBase.GetCurrentMethod.Name
                    strErr = String.Format("{0}:{1}", strSubroutineName, ex.Message)
                    objPart = AddPartAsSqlErrorMsg(strErr _
                                                   , _inputOptions _
                                                   )
                End Try

            Else
                'found load up the current values

                Dim p As Dictionary(Of String, Object) = objSpCommandList(0)

                objPart = AddPartFromSpList(p _
                                            , _strRanking _
                                            , _strOriginalRanking _
                                            , strLineGroupItemOptionList _
                                            , _inputOptions _
                                            )

            End If
            GetBomidData = objPart

        End Function

     Public Shared Function GetBOMListingStart(ByVal _parentPart As Part _
                                                   , ByVal _bomid As SqlInt64 _
                                                   , ByVal _ranking As SqlString _
                                                   , ByVal _originalRanking As SqlString _
                                                   , <SqlFacet(Precision:=18, Scale:=5)> ByVal _deltaX As SqlDecimal _
                                                   , <SqlFacet(Precision:=18, Scale:=5)> ByVal _deltaY As SqlDecimal _
                                                   , <SqlFacet(Precision:=18, Scale:=5)> ByVal _deltaZ As SqlDecimal _
                                                   , ByVal _inputOptions As Input _
                                                   , ByVal _lineGroupItemOptionsList As LineGroupItemOptionList _
                                                   , ByVal _partPriceAggregateList As PartPriceAggregateList _
                                                   , ByVal _autoRuleList As AutoRuleList _
                                                   , ByVal _partAttributeList As PartAttributeList _
                                                   , ByVal _partAttributeMethodList As AttributeMethodList _
                                                   , ByVal _lineGroupItemOptionRankingList As LineGroupItemOptionRankingList _
                                                   , ByVal _completePartList As PartList _
                                                   , ByVal _processStepsList As ProcessStepList _
                                                   , ByVal _spList As Dictionary(Of String, List(Of Dictionary(Of String, Object))) _
                                                   ) As PartList
            Dim objPartList As PartList = New PartList

            Dim conn As SqlConnection

            conn = New SqlConnection(_inputOptions.IntegratedSecurityConnectionString)

            'conn.Open()
            _inputOptions.IntegratedContextConnection = conn

            Dim threadConn = New SqlConnection(_inputOptions.IntegratedSecurityConnectionString)
            threadConn.Open()

            objPartList.AddRange(GetBomListingNEW(_parentPart _
                                                  , _bomid _
                                                  , String.Empty _
                                                  , String.Empty _
                                                  , 0 _
                                                  , 0 _
                                                  , 0 _
                                                  , _inputOptions _
                                                  , _lineGroupItemOptionsList _
                                                  , _partPriceAggregateList _
                                                  , _autoRuleList _
                                                  , _partAttributeList _
                                                  , _partAttributeMethodList _
                                                  , _lineGroupItemOptionRankingList _
                                                  , _completePartList _
                                                  , _processStepsList _
                                                  , _spList _
                                                  , threadConn _
                                                  ))

            GetBOMListingStart = objPartList

            'conn.Close()

        End Function

    Tuesday, December 03, 2013 1:42 PM
  • I've encountered problems with threading inside SQL Server CLR procedures as well.  I solved the problems by eliminating code dependencies on .NET 4.5.  I suggest that you run the following query and verify that all references in your code are to classes which are part of the .NET frameworks equal to or prior to the .NET framework that your SQL Server is running.

    select * from sys.dm_clr_properties

    If you are running SQL Server prior to 2012 then I'm pretty certain that you do have a problem because you are referencing classes in the 4.0 .NET framework in the code you've included.  SQL Server 2008 uses the 3.5 .NET framework and the Task class is a 4.0 .NET framework feature.  I actually encountered a very similar problem prior to upgrading to SQL Server 2012.

    My solution was to rewrite my CLR procedure using only .NET framework 3.5 classes which wasn't difficult.

    Tuesday, December 03, 2013 2:33 PM
  • This

                Catch ex As Exception
                     Dim i As Integer
                     Dim strErr As String = ex.Message
                     Dim objPartErr As Part
                     objPartErr = AddPartAsSqlErrorMsg(strErr, _inputOptions)
                     objBomList.Add(objPartErr)
                     i = 0
                 End Try 

    Looks wrong. Task.WaitAll may throw an AggregateException which you must unpack.  In any case you should probably throw instead of eating the exception.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, December 03, 2013 2:51 PM
  • I would use threads, not tasks.

    Tuesday, December 03, 2013 3:34 PM
  • if use threads how would I change up the code?

    Something like this?  but if so, how do I get my list back from the GetBomListingNew?

                    Dim worker As New Thread(New ThreadStart(Function() BOMThread.GetBomListingNEW(objPart _
                                      , objBomid _
                                      , strRanking _
                                      , strOriginalRanking _
                                      , objPart.DeltaXOutput _
                                      , objPart.DeltaYOutput _
                                      , objPart.DeltaZOutput _
                                      , _inputOptions _
                                      , _lineGroupItemOptionsList _
                                      , _partPriceAggregateList _
                                      , _autoRuleList _
                                      , _partAttributeList _
                                      , _partAttributeMethodList _
                                      , _lineGroupItemOptionRankingList _
                                      , _completePartList _
                                      , _processStepsList _
                                      , _spList _
                                      , ChildConnection _
                                      ) _
                                  ))
                    worker.Start()
                    worker.Join()

    Tuesday, December 03, 2013 6:04 PM
  • Tasks is the more modern API and should work fine.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, December 03, 2013 6:07 PM
  • ok, I updated everything in the CLR to 4.0 which is what I was using in sql (select * from sys.dm_clr_properties), but still randomly I don't always get all of my records from the task.WaitAll...any other suggestions? I'll check my try catch code to see if I am missing an error but it is just so random...I might run things 10 times and 1 or 2 out of 10 I'll just get part of the records...If for example I know I should be getting 250 records...I might get 110, or 150 2 of the 10 times.
    Wednesday, December 04, 2013 2:24 PM
  • I put extra handling in form AgregateException after I setup the TaskList(i) = TaskPartsArray(i) and I still randomly get less than all of the records I know I should return...80% of the time it returns all data, 20% it returns less than all of the data but with no error...

    Snippet of the code below. The GetBOMListingNew function returns a list of parts that I get from the db...and it can call itself recursively...

     Dim objBomid As Int64

                Dim n As Integer = objBomidList.Count - 1
                Dim TaskList As Task(Of List(Of Part))() = New Task(Of List(Of Part))(n) {}
                Dim TaskPartsArray As Task(Of List(Of Part))() = New Task(Of List(Of Part))(n) {}

                Dim TaskPartsList As List(Of Part) = Nothing

                Dim ConnectionArray(n) As SqlConnection

                Try


                    For i As Int32 = 0 To n


                        Dim ChildConnection = New SqlConnection(_inputOptions.IntegratedSecurityConnectionString)
                        ConnectionArray(i) = ChildConnection
                        ChildConnection.Open()
                        objBomid = objBomidList(i)
                        TaskPartsArray(i) = Task(Of List(Of Part)).Factory.StartNew(Function() BOMThread.GetBomListingNEW(objPart _
                                          , objBomid _
                                          , strRanking _
                                          , strOriginalRanking _
                                          , objPart.DeltaXOutput _
                                          , objPart.DeltaYOutput _
                                          , objPart.DeltaZOutput _
                                          , _inputOptions _
                                          , _lineGroupItemOptionsList _
                                          , _partPriceAggregateList _
                                          , _autoRuleList _
                                          , _partAttributeList _
                                          , _partAttributeMethodList _
                                          , _lineGroupItemOptionRankingList _
                                          , _completePartList _
                                          , _processStepsList _
                                          , _spList _
                                          , ChildConnection _
                                          ) _
                            )
                        TaskList(i) = TaskPartsArray(i)



                    Next

                Catch ex As AggregateException
                    Dim objPartErr As Part
                    Dim strErr As String
                    For Each ex In ex.InnerExceptions
                        strErr = ex.Message
                        objPartErr = AddPartAsSqlErrorMsg(strErr, _inputOptions)
                        objBomList.Add(objPartErr)
                    Next

                End Try



                Try
                    Task.WaitAll(TaskList)
                    For ii As Int32 = 0 To n
                        TaskPartsList = TaskPartsArray(ii).Result
                        objBomList.AddRange(TaskPartsList)
                    Next

                    For ii As Int32 = 0 To n
                        ConnectionArray(ii).Close()
                        SqlConnection.ClearPool(ConnectionArray(ii))
                    Next
                Catch ex As Exception
                    Dim i As Integer
                    Dim strErr As String = ex.Message
                    Dim objPartErr As Part
                    objPartErr = AddPartAsSqlErrorMsg(strErr, _inputOptions)
                    objBomList.Add(objPartErr)
                    i = 0
                End Try

    Monday, December 16, 2013 7:22 PM
  • You my find this useful, at least for ideas. http://sqlblog.com/files/folders/beta/entry29021.aspx It's non trivial to mess with thread management in SQL Server with SQLCLR. As you've already discovered.

    Hope this helps, Bob

    Tuesday, December 17, 2013 8:44 AM
    Moderator
  • Try with a Catch block that just rethrows the exception to ensure your error handling is not hiding the exception.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, December 17, 2013 3:10 PM
  • David, can you give me a few lines of code to show me what u mean by that?

    Thanks

    Tuesday, December 17, 2013 3:59 PM
  • Just something like this:

                Catch ex As AggregateException
                     Throw ex
                     'Dim objPartErr As Part
                     'Dim strErr As String
                     'For Each ex In ex.InnerExceptions
                     '    strErr = ex.Message
                     '    objPartErr = AddPartAsSqlErrorMsg(strErr, _inputOptions)
                     '    objBomList.Add(objPartErr)
                     'Next
    
                 End Try
    
    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, December 17, 2013 4:03 PM
  • k, thanks...not sure what that will do inside a CLR function
    Tuesday, December 17, 2013 4:04 PM
  • >not sure what that will do inside a CLR function

    It will throw an exception, which will be converted to a SQL Error and the query will fail.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, December 17, 2013 4:09 PM
  • I did this and ran it to see if I would get error, etc...got no errors...did some comparisons of the data...where one time I was supposed to get 266 records, I might get 150, etc...looks like a specific TaskList item just doesn't return anything...I add all of the tasks to the TaskPartsArray() and then I do a Task.WaitAll(TaskList)...but randomly some tasks aren't completed after the Task.WaitAll.  This is what I was assuming was happening. anyone have any ideas on how to be sure all of the tasks are really completed after a Task.WaitAll isn't really working?
    Tuesday, December 17, 2013 8:01 PM
  • If I comment out all of the thread code and just do this code, I get all of my records everytime but no threading...


                For i As Int32 = 0 To objBomidList.Count - 1
                    objBomid = objBomidList(i)

                    objBomList.AddRange(GetBomListingNEW(objPart _
                                      , objBomid _
                                      , strRanking _
                                      , strOriginalRanking _
                                      , objPart.DeltaXOutput _
                                      , objPart.DeltaYOutput _
                                      , objPart.DeltaZOutput _
                                      , _inputOptions _
                                      , _lineGroupItemOptionsList _
                                      , _partPriceAggregateList _
                                      , _autoRuleList _
                                      , _partAttributeList _
                                      , _partAttributeMethodList _
                                      , _lineGroupItemOptionRankingList _
                                      , _completePartList _
                                      , _processStepsList _
                                      , _spList _
                                      , _threadConn _
                                      ) _
                        )

                Next

    Tuesday, December 17, 2013 8:34 PM
  • was doing more testing...setup some sql to call the CLR function returning data, etc in a loop 50 times...about 10 of the 14 times it ran I got all of my records...3 times it didn't return all data but got no errors...THEN it stopped before going 50 times with this error:

    Msg 10312, Level 16, State 49, Line 54
    .Net Framework execution was aborted.  The UDP/UDF/CLR type did not revert thread token.

    Tuesday, December 17, 2013 8:57 PM
  • So far when I run this same script on a 4 processor server, I don't ever see that error but 2-3 out of 50 times, I didn't get all of my records either (no error at all). But I get the error (...did not revert thread token...) during the loop of 50 times on my local single processor box.
    Tuesday, December 17, 2013 9:24 PM
  • fyi, just got the .Net Framework execution was aborted.  The UDP/UDR/CLR type did not revert thread token on the 4 processor server...thought it might only be on a single processor machine.
    Tuesday, December 17, 2013 9:50 PM
  • While debugging the CLR in Visual Studio 2012...I had it to print out the Task.State for each of my tasks after the Task.WaitAll(TaskList) and they all show this:
    0 RanToCompletion {5}
    1 RanToCompletion {5}
    2 RanToCompletion {5}
    3 RanToCompletion {5}
    4 RanToCompletion {5}
    5 RanToCompletion {5}
    6 RanToCompletion {5}
    and then I had it print out objBOMList.Count...I got 224 which I should have gotten 226...but all tasks show RanToCompletion.
    I even had it to print out the TaskPartsArray(ii).Exception...it was 'Nothing' even when I didn't get all of my rows.

    Help...

    Friday, December 20, 2013 8:21 PM
  • Next thing I would to is debug the return values of your function, something like this:

                    For ii As Int32 = 0 To n
                         TaskPartsList = TaskPartsArray(ii).Result
                         if TaskPartsList.Count = 0 then 
                             Throw New InvalidOperationException("Empty TaskPartsList")
                         end if
                         objBomList.AddRange(TaskPartsList)
                     Next
    David


    David http://blogs.msdn.com/b/dbrowne/

    Friday, December 20, 2013 9:27 PM
  • the problem is that the Count <> 0...it is something but not the total it should be...

    Friday, December 20, 2013 9:29 PM
  • I had it print out the TaskPartsArray(ii).IsFaulted value too...all Nothing

    Friday, December 20, 2013 9:30 PM
  • the problem is that the Count <> 0...it is something but not the total it should be...

    So GetBomListingNEW() sometimes returns the "wrong" number of items?

    You're passing a lot of references to that function, and not all of those are types that have "value semantics".  So it's possible that the concurrent threads are interfering with each other in nasty ways because of shared objects, either passed in as references or used from a shared scope.  In other words, GetBomListingNew may not be "thread safe".

    David


    David http://blogs.msdn.com/b/dbrowne/

    Saturday, December 21, 2013 12:18 AM
  • Hi Jeff,

    What version of windows OS you are running this test on?

    __Raman

    Saturday, December 28, 2013 6:34 PM
  • _Raman,

    on my local box it is Windows 7 Enterprise, on the 4 processor server we are running Windows Server 2012 R2.

    Monday, December 30, 2013 1:31 PM
  • Surely calling a Shared (Static) method from multiple threads is going to cause synchronization issues. And by using a synchronization technique, Synlock or mutex you would be loosing the effectiveness of multiple threading.

    Monday, December 30, 2013 5:10 PM
  • what do you suggest I do to fix the issue so that I can multi-thread?
    Monday, December 30, 2013 5:12 PM
  • >what do you suggest I do to fix the issue so that I can multi-thread?

    The simplest rule of thumb is to have your concurrent tasks only use read-only data and local variables.  In GetBomListingNEW() ensure that it doesn't write any data to shared objects or collections.  Each thread should just read shared data and add items to a new List.  Then the returned lists are aggregated by the main thread.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Monday, December 30, 2013 5:36 PM
  • Hello Jeff,

    Looking at the age of the thread post and also noticed that multiple people have spent good amount of time on this but looks like you have not reseived the answer you were looking for.

    In that case, I would suggest opening a support ticket with Microsoft so that an engineer can work with you dedicatedly and can answer your questions.

     

    Thanks

    __Raman

    Monday, January 06, 2014 5:47 AM
  • what is the process of me opening up a ticket?
    Monday, January 06, 2014 1:35 PM
  • David,

    currently my GetBOMListingNew() passes in parameters which are collections of lists that get added to...for example I pass in part A to GetBOMListingNew() and along with that is a parameter objAutoRuleList which is a list of autorules...When processing Part A in the GetBOMListingNew() I find out from the db if Part A has any autorules in the db, if so, I add them to that objAutoRuleList collection so that it can be referenced if need be.  Are you saying things like this should not be occuring?

    High Level....Part A has 3 children AA, AB, and AC...after I get Part A I create three tasks one for each child AA, AB, AC...so when I am running each task, task AA's data doesn't reference task AB or AC's data but the collections might get updated...

    Tuesday, January 07, 2014 3:00 PM
  • >currently my GetBOMListingNew() passes in parameters which are collections of lists that get added to...

    >Are you saying things like this should not be occuring?

    Yes.  That is what I'm saying.

    If multiple threads try to add items to a single list, the behavior is not documented or guaranteed.  See, eg:

    "Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe. "

    List<t>Class</t>

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, January 07, 2014 3:29 PM
  • first I tried to change my Public shared Function GetBOMListingNew() to public static function...Methods cannot be declare static
    Tuesday, January 07, 2014 3:50 PM
  • "Shared" methods in Visual Basic is the same a "static" methods in C#.  But that will have no effect on the threading behavior.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, January 07, 2014 3:52 PM
  • David, so far you have been the only one that seems to have a potential grasp on what I am trying to do here and maybe a solution for it. My issue is how do I restructure this CLR to work with threading. Since it is called recursively, how do I structure my code so that I can have read-only data being passed in recursively?

    Since each time I go into the 'entry point' of the recursion GetBOMlistingNew() I may return more data, etc.

    Wednesday, February 12, 2014 1:43 PM
  • The mantra for threaded programming is to use immutable objects.  Any time you have a global or captured object that can be mutated, you introduce the need for expensive and error-prone synchronization between your threads.

    Just ensure that each object is either immutable, or local in scope.  Then you don't have to worry about synchronization.  Do not have any global collections that multiple threads access.  Just pass read-only data to each function and accumulate all the results in your outer method.

    For you, that means passing each task read-only objects, and have it return data, not copy it to a shared list. 

    So ensure that BOMThread.GetBomListingNEW does not modify any of its arguments or any global (shared) objects.

    David


    David http://blogs.msdn.com/b/dbrowne/

    • Marked as answer by Jeff573 Wednesday, February 12, 2014 7:12 PM
    Wednesday, February 12, 2014 3:19 PM