jvcoach23 Posted February 3, 2005 Posted February 3, 2005 I've been doing some research on Threading and am trying to give it a go. In my sample code, i have everything working except the return values are not getting passed back to the parent thread to be displayed in the list box. the debug.writeline tells me I'm getting the database back.. but can't get it to the parent thread. what am I doing wrong. Option Strict Off Imports System.Threading Imports System.Data Imports System.Data.SqlClient Public Class Form3 Inherits System.Windows.Forms.Form #Region " Windows Form Designer generated code " Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load End Sub Dim t1 As Thread Dim t2 As Thread Dim WithEvents oSquare As SquareClass Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim oSquare1 As New SquareClass Dim oSquare2 As New SquareClass t1 = New Thread(AddressOf oSquare1.CalcSquare) t1.Name = "Data" oSquare1.vcTableName = "tblPMData" t2 = New Thread(AddressOf oSquare2.CalcSquare) t2.Name = "Company" oSquare2.vcTableName = "tblPMCompany" t1.Start() t2.Start() If t1.Join(500) Then Me.ListBox1.Items.Add(oSquare1.Result) End If If t2.Join(500) Then Me.ListBox2.Items.Add(oSquare2.Result) End If End Sub Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click t1.Abort() End Sub Public Class SquareClass Private mvalue As Integer Private msquare As Double Private mvcTableName As String Private mresult As Integer Public Event threadcomplete(ByVal Result As Integer) Public Property vcTableName() As String Get Return mvcTableName End Get Set(ByVal pValue As String) mvcTableName = pValue End Set End Property Public Property Result() As Integer Get Return mresult End Get Set(ByVal pValue As Integer) mresult = pValue End Set End Property Public Property value1() As Integer Get Return mvalue End Get Set(ByVal pValue As Integer) mvalue = pValue End Set End Property Public Property square() As Double Get Return (msquare) End Get Set(ByVal pSquare As Double) msquare = pSquare End Set End Property Public Sub CalcSquare() SyncLock GetType(SquareClass) Dim cn As SqlConnection Dim cm As New SqlCommand Dim result As Integer cn = New SqlConnection("pwd=idontusesa; UID=sa; server=Ntdts1; database=Monitor") cm = New SqlCommand With cm .Connection = cn .CommandType = CommandType.Text .CommandText = "select count(1) from " & vcTableName End With cn.Open() Try result = cm.ExecuteScalar Catch ex As Exception Throw ex End Try cn.Close() Debug.WriteLine(result) 'MsgBox(vcTableName & " had " & CType(result, String) & " rows") RaiseEvent threadcomplete(result) End SyncLock End Sub End Class Private Sub oSquare_threadcomplete(ByVal Result As Integer) Handles oSquare.threadcomplete MsgBox(Result) End Sub Quote JvCoach23 VB.Net newbie MS Sql Vet
IcingDeath Posted February 4, 2005 Posted February 4, 2005 cant you do smthng like this: Class Square Public Event Done(Sender as Square) Dim T as System.Threading.Thread ' ' ' ' ' 'Your properties and whatever else ' ' Public Sub CalculateSquare() T=New System.Threading.Thread(Addressof CalcSquare) T.Start end sub Private CalcSquare() ' Do your Stuff Raiseevent Done(Me) End Sub End Class Quote
Optikal Posted February 4, 2005 Posted February 4, 2005 I think you should be setting mresult in your CalcSquare() code, NOT declaring a local result var (which btw has the same name as the public property, a big no-no). Quote
jvcoach23 Posted February 7, 2005 Author Posted February 7, 2005 Ok.. I have it working (part way). I can't get the raiseevent to fire. I tried to set a break point.. and it hits that break point but never fires off the event. Am I creating the event correctly. Public Class Form4 Inherits System.Windows.Forms.Form Dim WithEvents oSquare1 As SquareClass Dim WithEvents oSquare2 As SquareClass Public Class SquareClass Private mvalue As Double Private msquare As Double Public Event threadcomplete(ByVal square As SquareClass) Public Property value() As Double Get Return mvalue End Get Set(ByVal pValue As Double) mvalue = pValue End Set End Property Public Property square() As Double Get Return (msquare) End Get Set(ByVal pSquare As Double) msquare = pSquare End Set End Property Public Sub CalcSquare() SyncLock GetType(SquareClass) square = value * value RaiseEvent threadcomplete(Me) End SyncLock End Sub End Class Sub SquareEventHandler(ByVal square As SquareClass) Handles oSquare1.threadcomplete MsgBox("Went to the handler") End Sub hope this is all the info needed to help me past the current obstacle. Quote JvCoach23 VB.Net newbie MS Sql Vet
jvcoach23 Posted February 7, 2005 Author Posted February 7, 2005 It's been a good day.. I have the threading working.. it also is returing the data back from the thread through an event... The question I have now.. if I start 2 threads.. and the first thread I start takes longer to run then the second thread.. should the second thread have to wait for the first thread to come back before it completes thread 2. I am passing in a value to each thread.. the first threads value = 3000. in the class that is called it is doing a thread.currentthread.sleep(value). The second thread has a value of 10. it looks like thread 1 always has to complete before thread 2. If I change the values around.. thread 2 = 3000 and thread 1 = 10, then thread 1 comes back.. than then thread 2 3 seconds later. one other thing.. do you have to clean up after the thread completes thanks Quote JvCoach23 VB.Net newbie MS Sql Vet
jvcoach23 Posted February 7, 2005 Author Posted February 7, 2005 here you go.. there isn't much code.. so i grabbed it all. Dim t1 As Thread Dim t2 As Thread Dim WithEvents oSquare1 As SquareClass Private Sub Form4_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Me.Label1.Text = Nothing Me.Label2.Text = Nothing End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click oSquare1 = New SquareClass t1 = New Thread(AddressOf oSquare1.CalcSquare) t1.Name = "tblPMData" oSquare1.value = 3000 Me.Label1.Text = "Thread 1 started Processing" t1.Start() t1.Sleep(10) t2 = New Thread(AddressOf oSquare1.CalcSquare) t2.Name = "tblPMCompany" oSquare1.value = 10 Me.Label2.Text = "Thread2 started Processing" t2.Start() Refresh() If t1.ThreadState.Stopped Then t1 = Nothing End If If t2.ThreadState.Stopped Then t2 = Nothing End If End Sub Public Class SquareClass Private mvalue As Double Private msquare As Double Public Event threadcomplete(ByVal square As Integer, ByVal thread As String) Public Property value() As Double Get Return mvalue End Get Set(ByVal pValue As Double) mvalue = pValue End Set End Property Public Property square() As Double Get Return (msquare) End Get Set(ByVal pSquare As Double) msquare = pSquare End Set End Property Public Sub CalcSquare() SyncLock GetType(SquareClass) square = value * value Debug.WriteLine(value) Thread.CurrentThread.Sleep(value) RaiseEvent threadcomplete(square, Thread.CurrentThread.Name.ToString) End SyncLock End Sub End Class Sub SquareEventHandler(ByVal square As Integer, ByVal thread As String) Handles oSquare1.threadcomplete Debug.WriteLine(square & " - Ran on thread " & thread) If thread = "tblPMData" Then Me.ListBox1.Items.Add(square) Me.Label1.Text = "Thread 1 finished Processing - " & Now.Second & " - " & Now.Millisecond Else Me.ListBox2.Items.Add(square) Me.Label2.Text = "Thread 2 finished processing - " & Now.Second & " - " & Now.Millisecond End If Refresh() End Sub End Class thanks.. shannon Quote JvCoach23 VB.Net newbie MS Sql Vet
jvcoach23 Posted February 7, 2005 Author Posted February 7, 2005 sorry about that.. nuked what you said in my replie.. Quote JvCoach23 VB.Net newbie MS Sql Vet
jvcoach23 Posted February 7, 2005 Author Posted February 7, 2005 What advantages does the adhandler give over using the raiseevent like I was doing in test code... which one should I be using??? From what little I've learned since you asked the question.. it looks like they do the same thing.. but what is the reason for 2 different ways of doing the same thing.. I'm not getting something am I. thanks shannon Quote JvCoach23 VB.Net newbie MS Sql Vet
Optikal Posted February 7, 2005 Posted February 7, 2005 One is declarative, one is imperative syntax. The advantage of using AddHandler is that you can easily have one method handle multiple events (I may be wrong, but I don't think this is possible with the declarative syntax). Quote
penfold69 Posted February 8, 2005 Posted February 8, 2005 Actually, with declarative syntax, you can handle multiple events by using a comma-delimited list of those events. Something like: Private Sub button_Click(.. blah blah args ..) Handles Button1.Click, Button2.Click, Button3.Click Quote
IcingDeath Posted February 8, 2005 Posted February 8, 2005 (edited) Actually, with declarative syntax, you can handle multiple events by using a comma-delimited list of those events. Something like: Private Sub button_Click(.. blah blah args ..) Handles Button1.Click, Button2.Click, Button3.Click Thats true but with the addhandler keyword you can managed controls created on run-time better... Plus if an object goes out of scope for the function that created it but there is still a referrence to it, the event will still fire. like if a function in a class creates an object by declaring it only in the scope of that function and then passes it to another class if addhandler was used in order to add an event handler in the first class, when the event fires the handler will be used. What is interesting is what happens if an event has multiple handlers? With what sequence will the handlers be called? Edited February 8, 2005 by IcingDeath Quote
IcingDeath Posted February 8, 2005 Posted February 8, 2005 What advantages does the adhandler give over using the raiseevent like I was doing in test code... which one should I be using??? From what little I've learned since you asked the question.. it looks like they do the same thing.. but what is the reason for 2 different ways of doing the same thing.. I'm not getting something am I. thanks shannon !!! U still need to use raiseevent with addhandler.... The difference is in the declaration of the object... Public Class MyClass Public Event SomeEvent() Public Sub MySub RaiseEvent SomeEvent() End Sub End Class AddHandler Style Private Sub Form1_Load(Blah) Dim MC as new MyClass addhandler MC.SomeEvent,addressof EventHandler end sub Private Sub EventHandler 'Blah End Sub With events style Dim WithEvents MC as new MyClass Private Sub EventHandler Handles MC.SomeEvent End Sub Quote
jvcoach23 Posted February 9, 2005 Author Posted February 9, 2005 was playing around some more... with something different within the threads... it appears that if I do a while loop in a thread i start, that the first thread works... going through this loop. However, the second thread I start doesn't appear to even run. So I'm doing to starts, but the threads only work if I take a the while loop inside the class that I call when I create the new thread. can you not do a while loop inside a thread Quote JvCoach23 VB.Net newbie MS Sql Vet
Optikal Posted February 10, 2005 Posted February 10, 2005 you can definately do a while loop inside a thread. Post your code and show us why you think the second thread isn't being executed. Quote
jvcoach23 Posted February 11, 2005 Author Posted February 11, 2005 you can definately do a while loop inside a thread. Post your code and show us why you think the second thread isn't being executed. the reasons I was thinking it was not firing is the debug.writeline I have in each thread. when I take the while loop out.. i see both debug statements come up.. if I have the while loop.. only one debug.writeline every shows up. The dataset that is being used to put information in for the threads has 2 rows in it. Public Class PerfmonClass Private mvcServer As String Private mvcCompany As String Private mvcCategoryName As String Private mvcCounterName As String Private mvcInstance As String Private mintTblPMInstanceId As Integer Public Event PerfmonThread() Dim WithEvents oCounter As New PerformanceCounter Dim Counter As Integer Public Property vcServer() As String Public Property vcCompany() As String Public Property vcCategoryName() As String Public Property vcCounterName() As String Public Property vcInstance() As String Public Property intTblPMInstanceId() As Integer Public Sub GetPerfmon() SyncLock GetType(PerfmonClass) 'setup the counter With oCounter .CategoryName = vcCategoryName .CounterName = vcCounterName .InstanceName = vcInstance .MachineName = vcServer .BeginInit() End With While 1 = 1 'poll the counter for the info Counter = oCounter.NextValue Debug.WriteLine(Counter & " - " & Thread.CurrentThread.Name.ToString) Thread.CurrentThread.Sleep(5000) End While End SyncLock End Sub End Class Public Sub GetServerCounters() oPerfmon = New PerfmonClass Dim ds As DataSet ds = wsPerfmon.spPMCategoryInfoForOneServer("home", "s011038home") Dim dr As DataRow For Each dr In ds.Tables(0).Rows Dim perfThread = New Thread(AddressOf oPerfmon.GetPerfmon) oPerfmon.intTblPMInstanceId = dr.Item("intTblPMInstanceId") oPerfmon.vcCategoryName = dr.Item("vcCategoryName") oPerfmon.vcCompany = "home" oPerfmon.vcCounterName = dr.Item("vcCounterName") oPerfmon.vcInstance = dr.Item("vcInstance") oPerfmon.vcServer = dr.Item("vcServer") perfThread.name = dr.Item("vcCategoryName") Debug.WriteLine(dr.Item("vcCounterName")) perfThread.start() Thread.Sleep(5) Next End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click GetServerCounters() End Sub right now I wasn't trying to raise any events... in the end.. I'll want this loop to write to a database each time it loops through... thanks for the help Shannon Quote JvCoach23 VB.Net newbie MS Sql Vet
Dill Posted February 14, 2005 Posted February 14, 2005 Multithreading to my knowledge can be easily mistaken to mean two processes running on the CPU concurrently. This is not the case. The two threads take it in turn to use processor time similar to two separate programs. If the processor is not told to free itself up for the other thread surely you will get one thread hogging all the process time. Having not used the currentthread.sleep I could not say if this frees the processor or not. Maybe an application.doevents in there would free up the processor for the other thread? Dill Quote
Wile Posted February 14, 2005 Posted February 14, 2005 If the processor is not told to free itself up for the other thread surely you will get one thread hogging all the process time. Having not used the currentthread.sleep I could not say if this frees the processor or not. Maybe an application.doevents in there would free up the processor for the other thread? Dill I believe you are wrong on that... The OS will automaticly pre-empt threads that are running and give other threads with the same priority a time slice. Only win3.1 had the cooperative multitasking where tasks could claim 100% cpu without being any change of being preempted. That mechanism has been fully abandoned in favor of pre-emptive multitasking (in both winNT and win95, with different levels of succes ;) ) where the OS can give time to other threads of the same priority after some time (20 milliseconds or so, i dont know how much) even though the original thread isnt finished yet. Note that this is the reason why using multithreading becomes a very hard job. You NEVER KNOWN WHEN your executing thead is pre-empted and another is activated. Making it very VERY important to use the correct thread safety mechanisms like ReaderWriterLock. Many crashes relating to multithreading can never be reproduced because the exact timeing can't be recreated. I can still get Word to crash using background printing on large documents, due to incorrect multithreading in Word (at least Word 2000, havent tried with latest version, but I dont have high hopes). Only if all other threads have lower priority can a thread keep the cpu occupied (even for such a configuration there are ways that the low prioirty threads do get some cpu time). The sleep does allow the OS to schedule a different thread. I believe even a sleep time of 0 allows the OS to pre-empt the executing thread and start running another thread. Returning to the origal thread when it has gone through its list of threads. Quote Nothing is as illusive as 'the last bug'.
jvcoach23 Posted February 14, 2005 Author Posted February 14, 2005 With what Wile has said.. and it sounds like it makes sense, why does this code While 1 = 1 'poll the counter for the info Counter = oCounter.NextValue Debug.WriteLine(Counter & " - " & Thread.CurrentThread.Name.ToString) Thread.CurrentThread.Sleep(5000) End While seem to no allow the second thrid thread to be started. Based on what you are saying.. the first thread.. the one the intial form is loaded with is running, on the button click it loops through and created thread #2 and that runs taking a slight pause when it hits the sleep command... but thread #3 never does fire.. it sounds like it should fire and start #3 regardlesss of the thread sleep command.. seeing how is it suppose to only sleep on the current thread...#2 Quote JvCoach23 VB.Net newbie MS Sql Vet
Wile Posted February 15, 2005 Posted February 15, 2005 With what Wile has said.. and it sounds like it makes sense, why does this code ... seem to no allow the second thrid thread to be started. Based on what you are saying.. the first thread.. the one the intial form is loaded with is running, on the button click it loops through and created thread #2 and that runs taking a slight pause when it hits the sleep command... but thread #3 never does fire.. it sounds like it should fire and start #3 regardlesss of the thread sleep command.. seeing how is it suppose to only sleep on the current thread...#2 I don't know how exactly the vb SyncLock works, but my guess is that it works the same as the lock statement in C#. The trick is that you pass it something (in your case you pass it the result of GetType(PerfmonClass)), and that every other SyncLock statement that is passed the same argument is halted, until the first one to lock on that particular object, unlocks it. Now in your case, the first thing you do is get a SyncLock in the GetPerfMon method, and you dont release it until the method is completely done, including the while loop. This means that once the first thread gets the SyncLock, all the other threads, will be waiting until the first thread unlocks, which is never as you loop forever. You can test this by putting a counter increment just before the synclock (Use the System.Threading.Interlocked.Increment method for this to make sure it is thread safe) and that counter should increase. You can even put a break point on that counter and step through the code, you will see that the 2nd and next threads will wait for the SyncLock method. The idea of the SyncLock is correct, you want to protect the member variables of the class from simultanious access by multiple threads, however, in the while loop, during the sleep, there is no need to keep the SyncLock. I suggest you take a good look at that loop and decide where the SyncLock is really needed to protect the member variables, and where it isn't needed, e.g. the sleep. If the lock is not needed, unlock it so other threads can enter. You might want to take a look at the ReaderWriterLock as it allows more control over how member variables are used (allowing multiple reads at the same time, but makes sure that only 1 thread can write at a time, without any thread reading). Quote Nothing is as illusive as 'the last bug'.
jvcoach23 Posted February 15, 2005 Author Posted February 15, 2005 thanks a million for the lesson.. I'll have to read it a couple times and try to get it goign tonight... thanks again for the explinations. Shannon Quote JvCoach23 VB.Net newbie MS Sql Vet
Wile Posted February 15, 2005 Posted February 15, 2005 I'll add a tip that can be usefull while debugging. While running your application, under the Debug -> Windows a lot of items are available. One of them is Threads (default key combo: ctrl + alt + H). Open this window. When your program is paused (e.g. on a breakpoint or just stepping through some code), you can view all the active threads in your application in that window. If the Location of a thread is somewhere in your code, you can double click on that line, and the debugger will show the exact location of where that thread is at that moment, that way you can view what all the different threads are doing. In most cases this is the easiest way to spot a deadlock or a situation like it seems to be in the code you posted above. When debugging the current code you should see 1 thread inside the loop, and other threads in the same function, but waiting for the SyncLock. Good luck ;). Quote Nothing is as illusive as 'the last bug'.
jvcoach23 Posted February 15, 2005 Author Posted February 15, 2005 (edited) thanks for the info.. I went in and did a ctrl + alt + H and the threads window came up.. but I'm trying to go through the menu's and don't see where you open it up from if you want to see what all else is there to view.. can you point me in the right direction I also took out the synclock.. and that got the looping to work.. got a lot to play with and learn about.. so once I do.. I'll have more questions.. hopefully you'll still be around to explain. thanks shannon Edited February 15, 2005 by jvcoach23 Quote JvCoach23 VB.Net newbie MS Sql Vet
Wile Posted February 16, 2005 Posted February 16, 2005 The menu is in under Debug->Windows (top item in the debug menu). But for some reason the list at development time has only 2 items (breakpoints and immediate) , but at run time there are about 10 of them, including the watches (1-4 and auto), call stack, threads, modules, memory, registers, all the fun things you really wish you'd knew how they work ;). Be carefull though. If you only took out the synclock, you can have two threads calling the Counter = oCounter.NextValue at the same time, giving strange results (e.g. you skip 1 value of counter, and use another one by 2 threads at the same time). Especially as Counter is a member variable of the class and can be accessed by all the threads running. If you only use that counter inside the while loop, it is saver to declare the counter inside the GetPerfmon method. Variables declared inside a method are always thread safe as another thread executing the same method, has its own instance of that variable. Member variables of classes however will always need protection as they can be accessed by all threads. Here is an experiment you might want to try (I'm assuming the current code, and at least 2 threads running the while loop). Put a breakpoint on the Counter = oCounter.NextValue and run the code. When the breakpoint hits, let the code proceed one line so the Counter value is updated, but don't execute the Debug.WriteLine yet (Use F10 to execute a single line). Do look up the current value of the Counter and write it down somewhere. Now go into the thread window, and Freeze (right mouse menu) the current thread. Press F5 to let the program run again (the frozen thread is still paused and won't run yet). What should happen now is that the thread remains frozen, and another thread will hit the breakpoin on the Counter = oCounter.NextValue line. Let this line execute, and let this thread execute the debug.writeline. Now go back into the thread window and Thaw (right mouse menu again ;) ) the thread you frozen earlier. Switch to this thread (also right mouse menu), and let it execute the debug.writeline statement. Instead of the original value of Counter the frozen/thawed threat got when it did Counter = oCounter.NextValue (you did write it down I hope ;) ), it now has the same value as the other thread. It is a bloody pain to reproduce, but race-conditions like these create the weirdest bugs you'll ever find as they are almost impossible to reproduce without spending hours on all possible timing scenario's. One nasty thing about debugging multithreaded applications, if threads are running at the same time, the debugger can switch over from one thread to another without telling you, putting you in a different location of the code without warning, keep a close eye on what thread you are looking in when debugging like that, it is very easy to get confused. Especially when using a sleep command there is a high chance of a thread switch (with sleep it is 100% chance I believe ;) ) Quote Nothing is as illusive as 'the last bug'.
jvcoach23 Posted February 16, 2005 Author Posted February 16, 2005 so if the sleep command is a high threat for thread switch.. and I want the thread to wait for several seconds before it runs again.. what are my options that would make it thread safe. Quote JvCoach23 VB.Net newbie MS Sql Vet
Wile Posted February 16, 2005 Posted February 16, 2005 The sleep itself is perfectly thread save, however there is a threat switch when you sleep. A threat switch in itself is perfectly OK, the OS does them and you can't prevent it even if you wanted to, maybe I wasn't clear about that. What must be done is making the rest of the code safe to correctly handle that all member variables can be accessed from two different threads at (virtually, see bellow) the same time if they arent protected. That means that yes, you have limit the actual reading/writing of member variables and make sure only 1 thread at a time can do that. However, make the blocking as small as possible so you block as little as possible or you might run into the problem you had before. Note that when a thread encounters a lock that it can't pass yet (another thread has it), the OS automatically switches to another thread, leaving the original thread at the lock statement. You said you removed the SyncLock. What I ment in my reply is: if you don't implement a locking mechanism at all, you will run into a lot of problems. Sadly multithreading isnt something you can trial and error until everything is fixed, as you will keep running into problems, some only appearing after 4 hours of execution or worse, after the customer started using it. You really have to look at how all the variables that can be shared between threads (like the member variables in a class) are accessed, and how to protect access to it, with the smallest scope possible. You need to use a locking mechanism (like SyncLock, or a critical section like the ReaderWriterLock) to make sure no 2 threads have access at the same time, but you also need to use it as little as possible to prevent other problems. I think nobody ever claimed multithreading was easier than very difficult. The concepts of multithreading (having 2 or more threads do the same thing at the same time and how to protect everything) are very hard to get a grip on. I've had quite some experience with multithreading in c++, and those concepts of threads, locks, etc still apply to .NET, but in c++ it took me about a year to get a good grip on the basic principles of having your code execute in two locations at the same time and how to protect your application against problems resulting from this. The .NET framework might make using threads and everything easier, however, the concepts behind them are still just as difficult. Don't try using multithreading just because you want a checkmark on a feature list. The amount of work it takes to get it right is rarely worth the extra feature. Only use it when you have no other option. On the virtually remark I made above: With virtually I mean that with a (old fashioned, pre-Intel hyperthreading) single CPU, the CPU can execute only 1 assembly statement at a time, but assigning a value to a parameter (single line of code in the editor) can be several lines of assembly. So dont assume that 1 line of code is always run without a thread switch half way the line of code. Quote Nothing is as illusive as 'the last bug'.
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.