2

The title is to make this easy to find for others having this error. I'm new to Threading, so this is really giving me heck. I'm getting this runtime error that crashed Cassini. This is code that I'm maintaining originally developed as a website project in VS 2003 and converted to a VS 2008 website project.

Important Info:

  • The number of objects in the manualEvents array is 128 in this case.
  • products is an array of Strings
  • Need to support .NET 2.0
For Each product As String In products
    If Not product.Trim().ToUpper().EndsWith("OBSOLETE") Then
        calls += 1
    End If
Next
Dim results(calls - 1) As DownloadResults
'Dim manualEvents(calls - 1) As Threading.ManualResetEvent '128 objects in this case.
Dim manualEvents(0) As Threading.ManualResetEvent
manualEvents(0) = New Threading.ManualResetEvent(False)
'NOTE: I don't think this will work because what is not seen here, is that
'    this code is being used to populate and cache a long list of products,
'    each with their own category, etc. Am I misunderstanding something?

'initialize results structures
'spawn background workers
calls = 0
For Each product As String In products
    If Not product.Trim().ToUpper().EndsWith("OBSOLETE") Then
        Dim result As New DownloadResults

        'manualEvents(calls) = New Threading.ManualResetEvent(False)
        'Moved above For Each after declaration of variable

        result.params.product = product
        result.params.category = docType
        'result.ManualEvent = manualEvents(calls)
        result.ManualEvent = manualEvents(0)
        result.Context = Me._context

        results(calls) = result

        Threading.ThreadPool.QueueUserWorkItem(AddressOf ProcessSingleCategoryProduct, results(calls))
        Threading.Interlocked.Increment(calls) 'Replaces below incrementation

        'calls += 1
    End If
Next

Threading.WaitHandle.WaitAll(manualEvents) 'CRASHES HERE

Thread Helper Function (for the sake of completion)

Public Shared Sub ProcessSingleCategoryProduct(ByVal state As Object)
    Dim drs As DownloadResults = CType(state, DownloadResults)
    Dim adc As New cADCWebService(drs.Context)

    drs.docs = adc.DownloadADC(drs.params.category, drs.params.product)
    drs.ManualEvent.Set()
End Sub
Chiramisu
  • 4,687
  • 7
  • 47
  • 77
  • When Googling "The number of WaitHandles must be less than or equal to 64" a lot of answers appear. – usr Jun 05 '12 at 21:15
  • @usr: Yep, already spent several hours going through them all. Like I said, new to Threading, so I'm sure I'm overlooking something. – Chiramisu Jun 05 '12 at 21:17

1 Answers1

4

You don't need an array of 128 manual events to check for completion of all 128 threads.

Create only one manual reset event and a plain integer starting at 128. Decrement that integer using Interlocked.Decrement at the end of ProcessSingleCategoryProduct, and only signal the event when the count reaches zero:

if (Interlocked.Decrement(ByRef myCounter) = 0) myEvent.Set();

Then declare only one Threading.ManualResetEvent as opposed to an array of them, and you can call WaitOne instead of WaitAll on it, and you are done.

See also usr's comment for an easier alternative in case you have .NET 4.

Jirka Hanika
  • 13,301
  • 3
  • 46
  • 75
  • @JirkaHanika: Yes, I read [this link](http://stackoverflow.com/questions/2702545/workaround-for-the-waithandle-waitall-64-handle-limit) already and just can't make much sense of how to put it all together since I'm still new to threading. :( – Chiramisu Jun 05 '12 at 21:21
  • @Chiramisu - Which .NET version do you need to support? – Jirka Hanika Jun 05 '12 at 21:23
  • @JirkaHanika: .NET 2.0, sorry I definitely should have specified that before. – Chiramisu Jun 05 '12 at 21:26
  • @Chiramisu - I added one more level of detail to the answer then. – Jirka Hanika Jun 05 '12 at 22:07
  • @JirkaHanika: Bear with me please, I'm really struggling to figure this out. I modified my code above. The commented out lines are replaced by the line(s) immediately after them. It's still broken and causing my Cassini to crash constantly leading me to believe I'm doing something terribly wrong. – Chiramisu Jun 05 '12 at 23:24
  • @Chiramisu - No problem. Let's fix on. The `Increment` belongs to the the end of the thread callback, otherwise you call it too early (before, not after, the thread being counted executes); and you must test its return value against the number of products and fire the event, only when all products' async calls have completed; see the updated answer (except that you will use `Increment`). You still increment `calls` non-atomically in one place (just a style issue because that code is single threaded). Other than that, OK. If you see any other errors, focus on the new error message. – Jirka Hanika Jun 06 '12 at 07:01
  • @JirkaHanika: I spent another several hours working on this and finally got a thread to work based on [this tutorial](http://www.switchonthecode.com/tutorials/csharp-tutorial-using-the-threadpool), but I still can't figure out how to make it work with multiple threads (which are controlled by the `ManualResetEvent` array right?). Is there not one `ManualResetEvent` per `QueueUserWorkItem`? Btw, I'm THOROUGHLY confused by the whole `Decrement` thing. Can't get that to work in the slightest. I'm afraid I'm going to need to see the ENTIRE code in order to understand. Sorry. :( – Chiramisu Jun 06 '12 at 22:21
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/12220/discussion-between-jirka-hanika-and-chiramisu) – Jirka Hanika Jun 06 '12 at 22:35