8

I have read that Windows 3.11 uses cooperative multitasking, which means that the OS does not do process scheduling whenever it wants, but rather the currently running process "yield" the execution to the OS whenever the process wants, and then the OS chooses the next process to run.

But what I want to know is what is this "yield" functionality, is it a Windows API function that a process must call in multiple places in the process's code?

4

4 Answers 4

17

It was done in the GetMessage call.

The heart of a windows application is something like:

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
}

Each iteration gets a message and dispatches it. If there isn't a message available, it blocks until a message is available. Under 16-bit Windows, the CPU was yielded while waiting for a new message.

Some other calls like Yield or SendMessage would work the same way. But GetMessage was the one that most GUI applications would have primarily used.

Source

1
  • 2
    Right. I vaguely recall long ago tracing through a GetMessage call (in SoftICE) until it returned into another application. That's the way I remember win16 cooperative multitasking. When you call GetMessage() windows might return to some other app, process their message, then later return to you after some other app makes a yielding call. GetMessage() was not the only yielding call. GetMessage() made your app and other apps coroutines of each other. Commented Jun 12, 2017 at 17:31
2

Aside from the explicit request to Yield(), there are other APIs provided by Windows which potentially have to wait for another process to provide an answer, like the heart of the message pump GetMessage(), asking a window for information with SendMessage() and many more.

1

In Windows 3.11 and in earlier MacOS versions, there is no preemptive multitasking where the OS takes away the CPU from your process to give other processes a chance to run. So if you don’t do anything, as soon as your process starts running, all other processes stop until your process is finished.

Here comes the Yield () function. It calls the OS and tells it to let other processes run. So you lose the CPU, and some other process gets it, until it calls Yield() itself. Eventually you get the CPU back and your call to Yield() returns.

If you check your clock, thr call to Yield may have taken any time from microseconds to many seconds, but it takes zero CPU time.

You need to be careful about how often you call Yield. If you set a million numbers in an array to 0 and Yield() after each number, you waste the overhead of a million yield calls. If you call Yield every 10 seconds while running some code for an hour, that is very rude because all the other processes are starved of CPU time. So this makes your application look good but end users hate you.

Note that if you have an event loop (What did the user do? Pressed a key on the keyboard? Clicked the mouse? Handle it!) the operating system will typically call Yield() for you, so while you wait for user input other processes can run. So you call Yield() yourself from time to time if user input starts a long running process.

And note that because you control at which times you lose the CPU, many more operations are atomic automatically. This is not true with preemptive multi-tasking which makes it harder to always get right.

0

But what I want to know is what is this "yield" functionality, is it a Windows API function that a process must call in multiple places in the process's code?

You mostly never need to call Yield(). Under normal circumstances you just handle the requested WM message and exit from the window procedure, and the control is then transferred to other tasks. Your procedure will be called again once there is a message for it to handle.

Now sometimes you need to perform the "blocking" operation, during which you can't return. Here you seemingly have a choice: either you call Yield() periodically, or you run this code:

    ret = (BOOL) PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
    /* if we got one, process it */
    if (ret) {
       TranslateMessage(&msg);
       DispatchMessage(&msg);
    }

PeekMessage() also yields.

So now the question is: why to write as much code, if I can just call Yield()? And the answer is: no, you actually can't. Don't use Yield(). The problem with Yield() is that actually it only switches to another task(s) if they have any messages to process. If they don't - Yield() returns immediately, so you may end up calling it in a busy loop. PeekMessage() AFAIK also adds some "sleep" if there are no messages, so it is already much better that Yield(). But the real culprit is in DispatchMessage(). Its a synchronous dispatcher that calls directly into the window procedure that is supposed to handle this message. And the most important thing here is that this may even be your own window procedure! The same one that does the blocking operation. Yield() never results in a recursive calls into your own window procedure, because the windows' scheduler knows it haven't returned yet, so it won't call it. Only the direct call with DispatchMessage() allows your window procedure to be recursively called, to handle all other events, like timers, redraw events etc. So that way your app stays alive while blocking. With just Yield(), at best only other apps stay alive, and at worst - you just call it in a busy loop.

1
  • Isn't allowing other processes to run and not having to deal with concurrency while your app is running exactly what the OP was asking for? Commented Oct 15 at 7:14

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.