0

I have a web application that uses threading that runs fine when compilation debug is set to true however as soon as it is set to false the page causes a spike in CPU and never loads.

I created a very simplified version of the code in order to test this issue which is below.

EDIT: Note that the aim of the Do While JobFinished < 1 Loop is to keep the page from loading until all the threads have completed so I can display the result of the calculation within the thread to the user. This doesn't seem to be a problem when compilation debug is set to true.

All help much appreciated.

Imports System.Xml
Imports System.IO
Imports System.Data.OleDb
Imports System.Data.SqlClient
Imports System.Data
Imports System.Math
Imports System.Threading

Partial Class _Default

    Inherits System.Web.UI.Page

    Protected MyThread As Thread
    Protected WithEvents myRequest As engineThread
    Protected JobFinished As Integer = 0

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        ltMessage.Text = "<root>"

        myRequest = New engineThread()
        MyThread = New Thread(AddressOf myRequest.callEngine)
        MyThread.Start()

        Do While JobFinished < 1
        Loop

        ltMessage.Text &= "</root>"

    End Sub

    Public Sub engineHandler1() Handles myRequest.ThreadComplete

        ltMessage.Text &= "It Works!"
        JobFinished = JobFinished + 1

    End Sub

    Public Class engineThread

        Public Event ThreadComplete()

        Public Sub callEngine()

            RaiseEvent ThreadComplete()

        End Sub

    End Class

End Class

My web.config is simply as follows

<configuration>
    <system.web>      
      <compilation debug="false" strict="false" explicit="true" targetFramework="4.0" />
      <httpRuntime targetFramework="4.0" executionTimeout="600" />
    </system.web>
</configuration>
DotNetDublin
  • 770
  • 1
  • 9
  • 23
  • Your title is incorrect. The correct title would be "compilation debug = true hides a threading issue" - you've always had a threading issue by having that empty `Do While` loop in `Page_Load`, but apparently it doesn't show up when debug is enabled. – Damien_The_Unbeliever Jul 17 '14 at 13:20
  • I should have explained the purpose of the Do While loop so I'll update my question now... – DotNetDublin Jul 17 '14 at 13:23
  • In your edit, you mention Threads (plural) - is that the case? – slippyr4 Jul 17 '14 at 13:27
  • In the simplified version there is just one thread but in the real world version there is four – DotNetDublin Jul 17 '14 at 13:32
  • 1
    Then you need to do what Basic said in his answer, except have an array of AutoResetEvent[] (one for each of your worker threads) and have your main thread wait for all of them to be signalled with WaitHandle.WaitAll - see http://msdn.microsoft.com/en-us/library/system.threading.waithandle.waitall(v=vs.110).aspx – slippyr4 Jul 17 '14 at 13:35

4 Answers4

3

This...

Do While JobFinished < 1
Loop

Is awful. It means one core will spin at full speed for no reason burning cycles.

Create an Event to signal when the JobFinished count changes...

Dim DoneEvent As New AutoResetEvent(False)

Do While JobFinished < 1
    DoneEvent.WaitOne(1000)
Loop

Then for in the worker...

myRequest = New engineThread()
MyThread = New Thread(Sub() 
                          myRequest.callEngine()
                          DoneEvent.Set()
                      End Sub)
MyThread.Start()

Also, there's no reason to use a thread here - you're creating a new thread, using it, waiting for it to finish (inefficiently) and then carrying on. Since you're blocking the original thread anyway, why not just use it for the job at hand?

Basic
  • 26,321
  • 24
  • 115
  • 201
  • There's no need to use a while loop anymore, if AutoResetEvents are used. Just call `DoneEvent.WaitOne()`. – antiduh Jul 17 '14 at 13:52
  • @antiduh Not neccesarily - I'm not waiting indefinitely, only for a second to allow us to handle situations where the event is never set for some reason, it's also a better model when you get multiple threads, but picking exactly the right approach in this case is not possible as threads shouldn't be used in the simple example – Basic Jul 17 '14 at 13:55
  • One - Read the poster's comments. He's got four background threads and needs to block the `page_load` until done. Plenty of reason to use threads, just not as exactly worded. – antiduh Jul 17 '14 at 13:57
  • Two - Presumably, the code that sets the event is the same code that increments JobFinished. If one doesn't happen, neither happens. There is no reason to loop over WaitOne(1000). – antiduh Jul 17 '14 at 13:59
2
Do While JobFinished < 1
Loop

Is not a great idea - that will waste as much CPU as one thread can. You can wait for your thread to end with the Thread.Join() method.

As to the precise cause of the problem, I suspect that in release mode, your threadComplete is never executing. Replace that infinite loop with a Join() and get rid of the the threadcomplete event and see if that solves your problem.

As an aside, the correct way to signal between threads would be a ManualResetEvent or AutoResetEvent object, not a bool variable.

slippyr4
  • 862
  • 7
  • 18
  • Bit of an echo in here - and only 15 seconds apart :) +1 – Basic Jul 17 '14 at 13:27
  • Indeed. Points to you for bothering to show the AutoResetEvent. I think it's probably overkill here though, just Join() on the thread should do. – slippyr4 Jul 17 '14 at 13:28
  • Yeah I didn't even consider Join which is probably the better option here. It's rare I'm actually waiting for threads to finish instead of giving them more work – Basic Jul 17 '14 at 13:29
  • Thanks - This is code I've inherited the maintenance of but I'll go off and do some reading on Thread.Join() – DotNetDublin Jul 17 '14 at 13:33
  • Thead.Join() is only going to be useful in your simplified case. See my comment above for the solution of efficently waiting for multiple threads to signal. – slippyr4 Jul 17 '14 at 13:36
1
JobFinished = JobFinished + 1
...
Do While JobFinished < 1
    Loop

This is not thread safe code. Irrelevant of compile settings. JobFinishes must be volatile (of which VB has no knowledge, so use MemoryBarier) and the +1 must be interlocked. Right now your code will likely miss updates and cause an infinite loop (which is exactly what you see).

I can guarantee you that whatever you're trying to do, it can be done without threads. The code you posted shows serious gaps in understanding MT. You'll get burned a lot before you'll walk. Did you go over Multithreading in Visual Basic? For example, the code you're showing here should never be written using a loop. This is exactly what Events are for, after all. Read Advanced Synchronization Techniques.

Community
  • 1
  • 1
Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
1

The other posters give the right way to solve this problem - block page_load using a lock such as an AutoResetEvent.

Talking to the direct problem with the question: why does turning off debug break the code? When you turn off debug, you're likely turning on optimizations.

The loop, as written, is most likely optimized to an infinite loop. The loop condition variable is never modified within the scope of the loop, and since no effort is made to use any sort of memory barrier, the compiler has full right to expect that the value will never change. The compiler is free to optimize that loop to an infinite loop because you are relying on undefined behavior.

Specifically, the compiler probably optimizes to something that would look like the following:

If JobFinished < 1 Then
     Do While True
     Loop
End If

Or in C#:

if ( JobFinished < 1 ) {
     while(true) {}
}

At the very least, you should mark the variable as volatile, however, you cannot do that in VB.

Instead, use functions that enforce memory barrier semantics - Thread.VolatileRead() and Thread.VolatileWrite():

In page_load:

Do While Thread.VolatileRead( ByRef JobFinished ) < 1 Then 
Loop

In engineHandler1

Thread.VolatileWrite( ByRef JobFinished, JobFinished + 1 )

That said, this is still an awful way to solve your problem. I only provide this answer so that you can understand the original underlying problem.

antiduh
  • 11,853
  • 4
  • 43
  • 66