Link to home
Start Free TrialLog in
Avatar of caraf_g
caraf_g

asked on

Stop 'n Start

Has anyone got any idea on how to achieve an equivalent of the example
below, but:

1 without using a form
2 without using a tight "doevents" loop? No, even without ANY loops.

here's what I'm trying to do:

Imagine a simple scenario. You've got a form with a button on it, Command1
and a timer, Timer1

Private Sub Command1_Click()

1 - Some code is executed.

2 - The processing reaches the next instruction. And then it stops. But the
thread is not locked.....
>InstructionX. The processing will stop here and wait

4 - The processing continues here

End Sub

Private Sub Timer1_Timer()

3 - Something is done here so that the lock on instruction 2 is lifted

End Sub


A perfect example of this can be achieved when you *do* use a form as
follows

Private Sub Command1_Click()

1 - Some code is executed

2 - AnotherForm.Show vbmodal

4 - The processing continues here

End Sub
Private Sub Timer1_Timer()

3 - AnotherForm.Hide
'or
3 - Unload AnotherForm

End Sub


Problem is, I do not want to use a form, as this takes away focus from the
current form
I've meddled around with Mutexes, Critical Sections
(the program keeps crashing with these?) and Semaphores, but have not been
able to achieve the desired result. As far as I understand their use, in the
place of instruction 2 I would have to use WaitForSingleObject? But in the
case of using a Mutex, that seemed to do nothing - processing continued
immediately at instruction 4, and in the case of semaphores I eventually got
something to work, but that locked the whole thread so that instruction 3
cannot be executed.

Many thanks

Pino



Avatar of caraf_g
caraf_g

ASKER

PS - it obviously *must* be possible because, behind the scenes, that is exaclty what VB does when you execute

AForm.Show vbModal

Basically, what I need to know is, what is it that happens "behind the scenes", and how can I harness this technique.
Hello Caraf G !!! <smile>

How about an API solution.. <wink>.

Declare Sub Sleep Lib "Kernel32" _
(byval dwMilliseconds as long)

Add:

do until mbooTimerSwitchSelected _
    Sleep(2000)
loop

to your Command1_Click Sub.


caraf_q, isn't this how an ordinary call works ;-))

Sub One
   Instruction1

   Call Two

   Instruction3
End Sub

Sub Two
   Instruction2
End Sub


But I see the problem ... you want to fire an event some time later, halt your program and have it continue afterwards ...

Nice problem, I'll think a weekend.


Mutexes, semaphores et al. won't do you any good unless you're actually running your application with multiple threads. Are you doing this?
Avatar of caraf_g

ASKER

Hello wsh2 <nudge, nudge>

The devil never sleeps <wink>

But seriously, sleep is not a solution. Sleep locks the whole thread:

Example

Command1_Click

Sleep -1 'forever, like snowwhite <g>

End Sub

Command2_Click

'Now, what can I do here to wake snow white up?

End Sub

What's more, Sleep locks the whole thread. I cannot even click on Command 2.


vindevogel:
This is the problem, You're talking about Sub1 and Sub2, where sub1 calls sub2. I'm not!

Sub1 is one command click. Sub2 is another. I want to stop in the middle of sub1, and in sub2 do something so that whatever has stopped in the middle of sub1 suddenly realises that it can continue on.


pjknibbs, No, everything's in the same thread. I'd come to that conclusion already :-(

Any ideas?
And why don't you wanna use a eternal loop with doevents in it ?

And, when you only load a form, without showing it ... it does not change focus.

Ok, Here you go...

1) Create a new project.

2) Add a Timer control and 2 Command buttons.

3) Add the following code in the DECLARATIONS SECTION of the form, run the program and click Command1...

    Public Semaphore As Integer
    Private Sub Command1_Click()
        Select Case Semaphore
            Case 3
                Debug.Print vbCrLf + "You just clicked the button!"
                Debug.Print "I'm going to wait for 15 seconds"
                Debug.Print "you can still click anything you want..."
                Semaphore = 2
                Timer1.Enabled = True
            Case 2
                Debug.Print vbCrLf + "You can't click this button yet..."
                Debug.Print "I'm still waiting on a semaphore."
                Debug.Print "Go do something else..."
            Case 1
                Debug.Print vbCrLf + "15 seconds are up!"
                Debug.Print "Here is the rest of the code..."
                Semaphore = 3
        End Select
    End Sub
    Private Sub Command2_Click()
        Debug.Print vbCrLf + "Yahoo! Button 2!"
    End Sub
    Private Sub Form_Load()
        Timer1.Enabled = False
        Timer1.Interval = 15000
        Semaphore = 3
    End Sub
    Private Sub Timer1_Timer()
        Timer1.Enabled = False
        Semaphore = 1
        Command1_Click
    End Sub



Cheers!
By the way, watch the debug window when you run the program... ;-)
Avatar of caraf_g

ASKER

mcrider, you've misunderstood.

In your example, the code doesn't HALT anywhere in the command1_click module.

I want it to halt, and only continue when I say so.


vindevogel,

when I only load a form and don't show it, the code doesn't halt. I don't want to use an eternal loop with doevents, simply because

AForm.Show vbModal

doesn't use an eternal loop either (or does it?). So it can be done.
Avatar of caraf_g

ASKER

New example, to clarify further

Private mstrTheThingybob As String
Private mblnTimedOut As Boolean

Private Function GetNextThingybob() As String

1 - Some code is executed.

2 - The processing reaches the next instruction. And then it stops. But the
thread is not locked.....
>InstructionX. The processing will stop here and wait

4 - The processing continues here
If mblnTimedOut Then
    Err.Raise 50001, , "The request has timed out"
Else
    GetNextThingybob = mstrTheThingybob
End If

End Sub

Private Sub Timer1_Timer()

If it's possible Then
    mstrTheThingybob = somevalue
    Goto Release
ElseIf Timeout period has not yet relapsed
    Exit Sub
Else
    mblnTimedOut = True
    Release
Release:
3 - Something is done here so that the lock on instruction 2 is lifted

End Sub





Code that uses this, calling this from, e.g. an .exe, getNextThingybob could be e.g. in a DLL

'Do some code here
strSomeString = getNextThingybob
No, I understood... and if you run it, you'll see that it does the next best thing, accomplishing the same thing WITHOUT halting...  Everything you want executed before your halt point would be in "Case 3" and everything you want executed after your halt point would be in "Case 1"...

No loops, No Doevents, No APIs...


Cheers!®©
Avatar of caraf_g

ASKER

sorry, rushed job:

Private Sub Timer1_Timer()

If it's possible Then
    mstrTheThingybob = somevalue
    Goto Release
ElseIf Timeout period has not yet relapsed
    Exit Sub
Else
    mblnTimedOut = True
    Goto Release
End If

'Yeah, I know, it can't actually get here. But... you know.
Exit Sub

Release:
3 - Something is done here so that the lock on instruction 2 is lifted

End Sub







Avatar of caraf_g

ASKER

"accomplishing the same thing WITHOUT halting"

But it has to halt, otherwise, in the new example code the processing would return to the statement

strSomeString = getNextThingybob

Which I do not want to happen.
Avatar of caraf_g

ASKER

<doh>
relapsed should be elapsed.
<blush>
By the way, there is no way to physically halt execution on a line of code within a function or subroutine in a single thread application without using a loop with DoEvents...


Cheers!®©
Avatar of caraf_g

ASKER

mcrider, yes there is!

AForm.show vbModal

Does exactly what it says on the tin!

Except.... it shows a form.

But somewhere down deep in the dungeons, it does something. Probably calls some APIs or something. I'd like to know what that something is.

Perhaps it does loop with doevents. But I doubt it somehow.
You've done the very thing I *really* dislike... Changing requirements midstream...

First you said you wanted this to occur in a command button.  My code accomplishes this... Then you change the requirement to be a function...

If you're going to stick with a function, you can not do what you want in VB without using a "DoEvent" loop in the function.

The problem with it being in a function is a single thread will not dispatch without a doevents.
You already said you didn't want to use a modal form in the opening question.
The Sleep API puts the current app to sleep, is there something that puts another app to sleep ? (other exe to sleep)

By the way, the single thread dispatch limitation is also why you don't get writes on multiple sockets to work without calling the DoEvents after writing to a winsock control....

Cheers!®©
Avatar of caraf_g

ASKER

"You've done the very thing I *really* dislike... Changing requirements midstream"

Sorry... but I haven't changed the requirements, I'm just finding it very difficult to express exactly what it is I am trying to achieve.

I have however made it very clear from the start that I want the code to HALT at a specific instruction. Your code doesn't achieve this.
Actually, the "AForm.show vbModal" method almost certainly *does* use the equivalent of a DoEvents loop at its core, because there's no other way it can work in a single-threaded application. The documentation for the API function DialogBox(), which also waits for user input before returning, explicitly states it uses its own message loop to process events--if the Windows API can do it, why can't Visual BASIC?
Avatar of caraf_g

ASKER

vindevogel, the Sleep API won't do it. I want the application to halt at the instruction, not lock up completely. It should still be responsive to other events.
Avatar of caraf_g

ASKER

"almost certainly *does* use the equivalent of a DoEvents loop "

Yes, you're probably right. But in that case I'd like to learn what exactly that equivalent is, because it seems to behave a lot better than a doevents loop. The application seems steadier, and not as much processing time seems to get eaten by whatever it is it's doing.

That's the real motivation behind my attempt to get to the bottom of this. Processing time is important in this application.

Thanks

Pino
ASKER CERTIFIED SOLUTION
Avatar of pjknibbs
pjknibbs

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Pardon me if I am restating another comment, but there were too many to read them all.  The problem that you are having with semaphores is that they are used to synchronize multiple threads, but VB doesn't truely do multithreading.

The only way to safely get a separate thread of execution in VB is to create an ActiveX EXE.  Since an EXE has it's own memory space and processor time allocation, it will continue to run will you main program is waiting on the MUTEX.

About the only way to solve this problem without creating a separate EXE is to use the loop approach or a form.  I agree that I would not use a form for possible focus issues, plus it takes up quite a bit of system resources which a loop doesn't.  I don't like the looping method either, because it ties up processor time by polling.

My suggestion.  If the timer code is very fast, use a loop.  If it takes a while, create a separate ActiveX EXE and use MUTEX semaphores.
Well since this function is INTERNAL to the VB interpreter, and not available to the VB program itself, I am assuming that VB is actually doing a Yield() or DirectedYield() call.

Here is the documentation on both from "C":

void Yield(void)
---------------------

The Yield function stops the current task and starts any waiting task.
Returns

This function does not return a value.

Comments

The Yield function should be used only when the application is guaranteed not to receive any messages.

Applications that contain windows should use a DispatchMessage, PeekMessage, or TranslateMessage loop rather than call the Yield function directly. The message-loop functions handle message synchronization properly and yield at the appropriate times.
See Also

DirectedYield, DispatchMessage, PeekMessage, TranslateMessage



void DirectedYield(htask)
------------------------------------
HTASK htask;


The DirectedYield function puts the current task to sleep and awakens the given task.

Parameter      Description

htask      Specifies the task to be executed.

Returns

This function does not return a value.

Comments

When relinquishing control to other applications (that is, when exiting hard mode), a Windows-based debugger should call DirectedYield, identifying the handle of the task being debugged. This ensures that the debugged application runs next and that messages received during debugging are processed by the appropriate windows.

The Windows scheduler executes a task only when there is an event waiting for it, such as a paint message, or a message posted in the message queue.
If an application uses DirectedYield for a task with no events scheduled, the task will not be executed. Instead, Windows searches the task queue. In some cases, however, you may want the application to force a specific task to be scheduled. The application can do this by calling the PostAppMessage function, specifying a WM_NULL message identifier. Then, when the application calls DirectedYield, the scheduler will run the task regardless of the task's event status.
DirectedYield starts the task identified by htask at the location where it left off. Typically, debuggers should use TaskSwitch instead of DirectedYield, because TaskSwitch can start a task at any address.

DirectedYield returns when the current task is reawakened. This occurs when the task identified by htask waits for messages or uses the Yield or DirectedYield function. Execution will continue as before the task switch.
DirectedYield is located in KRNL286.EXE and KRNL386.EXE and is available in Windows versions 3.0 and 3.1.
See Also

PostAppMessage, TaskSwitch, TaskGetCSIP, TaskSetCSIP, Yield



Cheers!®©
Actually, there's another reason why my earlier GetMessage() loop would be preferable to a DoEvents loop--GetMessage() actually puts the application to sleep until a message arrives, so you're not actually doing any processing unless something's happening which requires it. DoEvents() is more akin to a PeekMessage() loop, which makes your application use 100% of CPU time regardless of what's actually happening.
Hi,
If you don't like doevents (or it doesn't exist, e.g. in VB for WinCE), but you want your app to respond to user input (mouse or keyboard), use this loop:

   Do
      If GetInputState() <> 0 then Exit Do
      Sleep 1 ' must be very short period, 500 msecs will probably mask some events
   Loop
Avatar of caraf_g

ASKER

Everybody....

Thanks very much for the overwhelming response.

However... the prize will go to pjknibbs. GetMessage is exactly what I was looking for. You da man. ;-)
Avatar of caraf_g

ASKER

OR, of course. WOman! <g>
Avatar of caraf_g

ASKER

Adjusted points to 150
caraf_g,

Did you actually get pjknibbs's solution to work?? When I try it, VB locks up tight...


Cheers!®©
Avatar of caraf_g

ASKER

Yes, I got it to work perfectly.

The way I did it was by loading a form with a timer on it (but, and that's important, NOT showing it).

Then I used GetMessage as follows (sorry for VB/C mix-match)

while(GetMessage(&msg, FormWithTimer.hwnd, 275&, NULL))
{
    'Do what I used to do in the _Timer procedure here
    'Decide whether to jump out of loop or not.
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

the nice thing about GetMessage is that you can choose which messages to process, which ones to ignore completely and which ones to pass on unaltered. It's got great potential, and I'm going to investigate it further.

Funny thing is, this is still a loop. But it's a much nicer loop. It only loops around when it is absolutely necessary. A DoEvents loop just loops aimlessly. I can accept this type of loop.