Pickle Posted May 22, 2007 Posted May 22, 2007 My goal is to wrap the multimedia functions into a class that operates similar to the timer control. I need something with a high accuracy and precision. It works but I dont think Im managing the threads right. Heres what I do: Imports System.Threading Imports System.Threading.Thread Public Class CTimer Private Declare Function timeGetTime Lib "winmm.dll" () As Integer Private Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer Private Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer Public Event TimerElapsed() Private CheckThread As Thread Private objLock As Object Private intInterval As Integer = 0 Private intStartTime As Integer = 0 Public Sub New() timeBeginPeriod(1) objLock = New Object End Sub Public Sub StartTimer(ByVal interval As Integer) intInterval = interval intStartTime = timeGetTime() CheckThread = New Thread(AddressOf Me.CheckTime) Thread.CurrentThread.Priority = ThreadPriority.Highest CheckThread.Start() End Sub Public Sub StopTimer() If Not CheckThread Is Nothing Then Try CheckThread.Abort() 'Causing a problem CheckThread = Nothing Catch ex As Exception MsgBox("CTimer Thread would not abort", MsgBoxStyle.Critical, "ERROR") End Try End If timeEndPeriod(1) End Sub Private Sub CheckTime() Dim start_time As Integer Dim interval As Integer SyncLock objLock start_time = intStartTime interval = intInterval End SyncLock While timeGetTime() - start_time < interval Sleep(1) End While RaiseEvent TimerElapsed() End Sub End Class So to summarize I start a thread that checks the time variables. Once the correct condition has been reached the event is fired. This goes to a handler that in turn starts the timer calling the StartTimer() member. This is the point where I think im going wrong. When I set CheckThread a second time, im blowing away from previous reference to the first thread? It should die on its own, but how do I know? If none of the threads die off, I could be infinitely creating new threads. Is there a better way to do this? Quote
Administrators PlausiblyDamp Posted May 23, 2007 Administrators Posted May 23, 2007 Exactly how high a precision do you need? Are we talking millisecond accuracy being good enough or something more accurate than that? Also be aware that calling .Abort on a thread doesn't guarantee it will exit there and then - plus it throws a ThreadAbortedException you will need to handle as well. Being honest using a high priority thread in a tight loop like you are doing though is not a good way to approach the problem as it will potentially drive the CPU @ 100% and interfere with the performance of other running applications. Would the built in System.Timers.Timer class not be sufficient for your needs? Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
MrPaul Posted May 23, 2007 Posted May 23, 2007 Threads When I set CheckThread a second time, im blowing away from previous reference to the first thread? It should die on its own, but how do I know? If none of the threads die off, I could be infinitely creating new threads. Is there a better way to do this? The thread will die when the event handler returns, as at this point it will also return from CheckTime and so terminate. Whether or not there is a reference to the thread will not affect the thread itself terminating, only whether there is a .Net Thread object associated with the thread. If the event handler never returns, then the thread will never die, but in this case it is not your problem to worry about - it is the responsibility of the handler. In fact your StopTimer method could abort a thread executing within the event handler, which is not very polite. To counter this you should probably be setting CheckThread to Nothing before raising the event, or even better, don't use Abort at all. This would be much better: Public Sub StopTimer() CheckThread = Nothing timeEndPeriod(1) End Sub Private Sub CheckTime() Dim start_time As Integer Dim interval As Integer SyncLock objLock start_time = intStartTime interval = intInterval End SyncLock While timeGetTime() - start_time < interval Sleep(1) 'Check if StopTimer has been called, and terminate the thread if so If CheckThread Is Nothing Then Return End While 'Disconnect thread when it enters the event handler CheckThread = Nothing RaiseEvent TimerElapsed() End Sub This highlights another problem with your code - timeBeginPeriod is called within the constructor, but timeEndPeriod is called within the StopTimer method. If StartTimer were invoked after StopTimer, there is no call to timeStartPeriod so the timer may not function properly. The class needs to keep track of this and either call timeStartPeriod again, or throw an InvalidOperationException if StartTimer is invoked after StopTimer. Having said all that, I have to agree with PlausiblyDamp that using a tight busy wait loop is not an ideal timing method. Good luck :) Quote Never trouble another for what you can do for yourself.
Pickle Posted May 23, 2007 Author Posted May 23, 2007 I do need the resolution around 1 ms, as im controlling a device to generate a waveform that has specific timing requirements. I tried using the timer control, but it wasnt accurate enough. Before you guys posted I came up with one solution. I added a list of thread and each time I create a thread I add it to the list. Each time I start the timer I look for stopped threads and remove them from the list. When I stop the timer I then go through the list to stop all of the remaining threads. I will look over your ideas some more to see if I can improve my class. Quote
Administrators PlausiblyDamp Posted May 23, 2007 Administrators Posted May 23, 2007 (edited) There is still a problem with your code in the fact that Sleep() doesn't have millisecond accuracy either :( You might find the timeSetEvent function more useful as it will take care of raising events based on elapsed time and appears to have the same accuracy as the other WinMM functionality. Edited May 23, 2007 by PlausiblyDamp Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Administrators PlausiblyDamp Posted May 23, 2007 Administrators Posted May 23, 2007 In fact having a rummage around the internet I came across this Project. One of the supporting classes is a timer Clicky that might give you a bit more information. Be aware though both the project and the class in question are licensed under the LGLP and as such you would be bound by it's terms and conditions if you choose to use the source. If you find the license too restrictive or whatever then the basic idea is use the CreateTimerQueue and related functions to generate your events. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Pickle Posted May 23, 2007 Author Posted May 23, 2007 There is still a problem with your code in the fact that Sleep() doesn't have millisecond accuracy either :( You might find the timeSetEvent function more useful as it will take care of raising events based on elapsed time and appears to have the same accuracy as the other WinMM functionality. Your right about the sleep, although it hasnt seemed to interfere yet. Im trying timSetEvent, but im having some problems with the Declare, what should the callback parameter be? Its a long, but I want to give the function my "AddressOf MyFunction". Quote
Pickle Posted May 23, 2007 Author Posted May 23, 2007 Scrap the timeSetEvent, the queue does look better. I wont be using anything below W2K so thats the one I think I will try. Getting the correct declare statements is challenge though. Quote
Pickle Posted May 23, 2007 Author Posted May 23, 2007 Ok i found an VB6 example at http://www.ex-designz.net/apidetail.asp?api_id=82 Which ive converted to VB.NET, it seems to work Heres the converted code: Public Class CQueueTimer Delegate Sub SubClassProcDelegate() Private Declare Function CreateTimerQueue Lib "kernel32.dll" () As Integer Private Declare Function CreateTimerQueueTimer Lib "kernel32.dll" (ByRef phNewTimer As Integer, _ ByVal TimerQueue As Integer, _ ByVal Callback As SubClassProcDelegate, _ ByVal Parameter As Integer, _ ByVal DueTime As Integer, _ ByVal Period As Integer, _ ByVal Flags As Integer) As Integer Private Declare Function DeleteTimerQueue Lib "kernel32.dll" (ByVal TimerQueue As Integer) As Integer Private Declare Function DeleteTimerQueueTimer Lib "kernel32.dll" (ByVal TimerQueue As Integer, _ ByVal Timer_Renamed As Integer, _ ByVal CompletionEvent As Integer) As Integer Private hQueue As Integer Private hTimer As Integer Private TimerCallBack As SubClassProcDelegate Public Sub New(ByVal callback As SubClassProcDelegate) TimerCallBack = callback hQueue = CreateTimerQueue() End Sub Public Sub CloseTimer() DeleteTimerQueue(hQueue) End Sub Public Sub StartTimer(ByVal time As Integer) If hTimer = 0 Then CreateTimerQueueTimer(hTimer, hQueue, TimerCallBack, 0, time, 0, 0) End If End Sub Public Sub StopTimer() If hTimer <> 0 Then DeleteTimerQueueTimer(hQueue, hTimer, 0) hTimer = 0 End If End Sub End Class Quote
Pickle Posted May 23, 2007 Author Posted May 23, 2007 Yet another reply.... I had to do a small update, my app was crashing when more than 1 timer was active, setting the flag EXECUTEINTIMERTHREAD fixed it. Heres the code: Private WT_EXECUTEDEFAULT As Long = &H0 Private WT_EXECUTEINTIMERTHREAD As Long = &H20 Private WT_EXECUTEINIOTHREAD As Long = &H1 Private WT_EXECUTEINPERSISTENTTHREAD As Long = &H80 Private WT_EXECUTELONGFUNCTION As Long = &H10 Private WT_EXECUTEONLYONCE As Long = &H8 Private WT_TRANSFER_IMPERSONATION As Long = &H100 CreateTimerQueueTimer(hTimer, hQueue, TimerCallBack, 0, time, 0, WT_EXECUTEINTIMERTHREAD) Quote
Mike_R Posted May 26, 2007 Posted May 26, 2007 Hey Pickle, I don't have much to add here because I'm not very strong with threading. But just to be sure, I assume you tried the 'System.Timers.Timer' or 'System.Threading.Timer', as opposed to the 'System.Windows.Timer', I assume? The 'System.Windows.Timer' has to be innacurate because it operates through the massage pump... The others should definately be more accurate -- but I still don't know if they'll meet your 1ms accuracy requirement. Just a thought... Ignore all this if you've already covered this base. :) Mike Quote Posting Guidelines Avatar by Lebb
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.