A preemptive scheduler must stop a program that is looping and not calling any operating system function. The program is not triggering any fault as division by zero. We are assuming that the interrupt are enabled and assuming single core processor. Only a timer interrupt can succeed to regain control of the processor. The rate set by this timer gives you the maximum switching time for the scheduler. The preferred value is 1000 Hz which gives 1 ms resolution. For computers not fast enough to allow so many timer interrupt per second, the next popular values were: 60 Hz, from the video card refresh rate 18 Hz, from a 16 bit overflow of IBM PC original timer chips ; color TV cristal of 3.59 MHz / 3 / 65536
Other option: cooperative multitasking. This is how apollo computer managed to run many tasks... As well as Windows 3.1 You call a lightweight scheduler everywhere your code perform long loops. The lightweight scheduler usually just decrement an integer and immediately return if the value still not zero. For the rare case where the integer reach zero, immediately set that integer to the default value and perform the next tests to decide if a new task with higher priority needs to be executed. If a precise timing is needed, then at this point, reading a hardware timer counter and comparing the value with the previous one allow to evaluate precisely the elapsed time.