Wait until a specified time in VB6

I've got some code running as a background process, implemented in VB6.  I'd like it to take some action once a day, say at 02:00 local time; it then simply sleeps until the next appropriate time unless something causes it to quit in the meantime.

I've tried using the Win32 Sleep() call, but this blocks the entire thread and the app won't shut down tidily.  The app should display no user interface; the idea is to run it as a service that cannot interact with the desktop as it must run as a non-system user identity.  So I'm not inclined to use a timer control.  I'm not sure about ccrpTimer either, as it appears to call back to th timed function on a different thread to the main thread.

Anyone got any suggestions as to how I can do this?

- Peter
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Guy Hengel [angelIII / a3]Billing EngineerCommented:
Although you don't need to use the form (display) when the application runs as service, you can still USE the form in invisible state to load the timer and use it's event. At least, this gives you a stable solution regarding starting/stopping the application/service
At least, this is what I do..
Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics TeacherCommented:
Here is basic code that will cause something to run once a day at the specified time:

' ----------------------------------------
' Module1
' ----------------------------------------
' (Go to Project --> Properties and
' set the "Startup Object" to "Sub Main"
' ----------------------------------------
Option Explicit

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Public Sub Main()
    If App.PrevInstance Then
        MsgBox "Another instance is already running", vbInformation, "Already Running"
        End ' kill this instance
    End If

    Dim targetTime As Date
    ' setup targetTime for today
    ' (change the time portion to suit your requirements)
    targetTime = CDate(Format(Now, "m/d/yyyy") & " 2:00 am")
    ' if it is past that time then set it up for tomorrow
    If Now > targetTime Then
        targetTime = DateAdd("d", 1, targetTime)
    End If
    ' just to show when the next targetTime is
    Debug.Print "targetTime = " & targetTime
    While True
        ' wait for the targetTime to pass...
        If Now < targetTime Then
            DoEvents ' keep app responsive
            Sleep 100 ' reduce CPU usage
            ' call your sub that does the desired processing
            Call OnceADayProcessing
            ' setup targetTime for the same time tomorrow
            targetTime = DateAdd("d", 1, targetTime)
            ' just to show when the next targetTime is
            Debug.Print "targetTime = " & targetTime
        End If
End Sub

Public Sub OnceADayProcessing()
    Debug.Print "OnceADayProcessing..."
End Sub

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
MelandraAuthor Commented:
Thanks, Idle Mind.  I was hoping for something that would sleep the thread unless/until an event came in, rather than polling, but that code appears to work and I can lengthen the poll interval.  I'm going to delay accepting that as *the* answer for 24 hours to see whether anyone can come up with a solution that doesn't poll - otherwise, the points are yours!

- Peter

P.S. Yeah, I know, I'm probably worrying needlessly on modern machines.
Your Guide to Achieving IT Business Success

The IT Service Excellence Tool Kit has best practices to keep your clients happy and business booming. Inside, you’ll find everything you need to increase client satisfaction and retention, become more competitive, and increase your overall success.

Sleep isn't really reliable for long durations.., however you can use an alternative which is SetWaitableTimer API.

egl1044, that would be OK if you could write it into a sample that doesn't use any forms or waiting loops.  I don't think that it's possible to do that.  I think that Idle_Mind's sample is as close as you are going to get to a solution with the stated user interface restriction--although I agree with angelIII's comment about invisible forms... no real reason not to have it.  You aren't designing an ActiveX component here with the "Unattended Execution" restriction; the NTSVC.ocx is designed to be placed on a form for that matter.
Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics TeacherCommented:
The example given by egl1044 WILL work WITHOUT any forms at all.  The form is just there to give you an easy way to compare the Sleep() call with the Wait() call they have written in the Module.  The Wait() function will work just as well when called from an app that starts with Sub Main().
> The example given by egl1044 WILL work WITHOUT any forms at all.

I didn'tt mean to say that it won't work.  But it isn't exactly what the requirements specified for no interface, no polling loops.

I can think of a couple ways it will work.
1. in a polling loop in sub main.  (this is how I tested it because the logic is simple)

Sub Main()
        Wait 10
        'test for time here
End Sub

2. specify the number of seconds until the execution of the code you want.  The loop in sub main is only executed once every 24 hours...

Sub Main
   'calculate the number of seconds until the next execute time and pass them to Wait function
       Wait NumSeconds

End Sub

However, the loop in the Wait function fires every time the application receives a message... that should be OK and is desirable to keep the application responsive, but it isn't quite the same as a timer control which only fires at certain intervals.  It's still a polling loop.

Of course if there's another way I'm not thinking of, please point it out.  

Neither method uses much in the way of CPU cycles, after compiling them and after running both under performance monitor.  Both methods are acceptable, and result in a responsive application.  The thing I like about your sample is that the full code is given for the set up of the date testing logic.  
Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics TeacherCommented:
I agree with your assessment PaulHews.

The only other option I can think of is to use your method #2 along with the SetTimer() and KillTimer() APIs.  But even with those, you will need some kind of continous loop to keep the Sub Main() from exiting and consequently the app from closing.  Then, to keep the CPU cycles down you would need to use the Sleep() API again which kinda invalidates the whole point of SetTimer() in the first place...

MelandraAuthor Commented:
Thanks to all for the discussion and for your time - the conclusion appears to be that polling is the way to go.  Idle Mind's solution was the earliest to point that out so, as stated above, I'm going with that as the solution to the question as stated.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic Classic

From novice to tech pro — start learning today.