Ответ 1
В Ubuntu имеется копия текущей (28 июня 2016 г.) реализации window.setTimeout()
.
Как мы видим, таймер вставляется этой строкой кода:
nsAutoPtr<TimeoutInfo>* insertedInfo =
mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
Затем несколько строк ниже имеют оператор if()
:
if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
...
insertedInfo == mTimeouts.Elements()
проверяет, был ли установлен таймер, который был только что вставлен. Следующий блок НЕ выполняет прикрепленную функцию, но основной цикл сразу же замечает, что таймер был отключен, и, таким образом, он будет пропускать состояние IDLE (выход процессора), которое вы ожидаете.
Это ясно (по крайней мере для меня) объясняет поведение, которое вы испытываете. Отрисовка на экране - это другой процесс (задача/поток), и для этого другого процессора необходимо отказаться, чтобы получить возможность повторно рисовать экран. Чтобы это произошло, вам нужно подождать достаточно долго, чтобы ваша функция таймера не выполнялась немедленно, и произошел выход.
Как вы заметили, пауза в 500 мс делает трюк. Вероятно, вы можете использовать меньшее число, например 50 мс. В любом случае это не будет гарантировать, что выход будет иметь место, но вероятность того, что это произойдет, если компьютер, на котором работает этот код, в настоящее время не загружен (т.е. Антивирус в настоящее время не работает на полной скорости в фоновом режиме... )
Полная функция SetTimeout()
из Firefox:
(расположение файла в источнике: dom/workers/WorkerPrivate.cpp
)
int32_t
WorkerPrivate::SetTimeout(JSContext* aCx,
dom::Function* aHandler,
const nsAString& aStringHandler,
int32_t aTimeout,
const Sequence<JS::Value>& aArguments,
bool aIsInterval,
ErrorResult& aRv)
{
AssertIsOnWorkerThread();
const int32_t timerId = mNextTimeoutId++;
Status currentStatus;
{
MutexAutoLock lock(mMutex);
currentStatus = mStatus;
}
// It a script bug if setTimeout/setInterval are called from a close handler
// so throw an exception.
if (currentStatus == Closing) {
JS_ReportError(aCx, "Cannot schedule timeouts from the close handler!");
}
// If the worker is trying to call setTimeout/setInterval and the parent
// thread has initiated the close process then just silently fail.
if (currentStatus >= Closing) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
newInfo->mIsInterval = aIsInterval;
newInfo->mId = timerId;
if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
NS_WARNING("Timeout ids overflowed!");
mNextTimeoutId = 1;
}
// Take care of the main argument.
if (aHandler) {
newInfo->mTimeoutCallable = JS::ObjectValue(*aHandler->Callable());
}
else if (!aStringHandler.IsEmpty()) {
newInfo->mTimeoutString = aStringHandler;
}
else {
JS_ReportError(aCx, "Useless %s call (missing quotes around argument?)",
aIsInterval ? "setInterval" : "setTimeout");
return 0;
}
// See if any of the optional arguments were passed.
aTimeout = std::max(0, aTimeout);
newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
uint32_t argc = aArguments.Length();
if (argc && !newInfo->mTimeoutCallable.isUndefined()) {
nsTArray<JS::Heap<JS::Value>> extraArgVals(argc);
for (uint32_t index = 0; index < argc; index++) {
extraArgVals.AppendElement(aArguments[index]);
}
newInfo->mExtraArgVals.SwapElements(extraArgVals);
}
newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval;
if (!newInfo->mTimeoutString.IsEmpty()) {
if (!nsJSUtils::GetCallingLocation(aCx, newInfo->mFilename, &newInfo->mLineNumber)) {
NS_WARNING("Failed to get calling location!");
}
}
nsAutoPtr<TimeoutInfo>* insertedInfo =
mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n",
this, aTimeout, aIsInterval ? "yes" : "no"));
// If the timeout we just made is set to fire next then we need to update the
// timer, unless we're currently running timeouts.
if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
nsresult rv;
if (!mTimer) {
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return 0;
}
mTimerRunnable = new TimerRunnable(this);
}
if (!mTimerRunning) {
if (!ModifyBusyCountFromWorker(true)) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
mTimerRunning = true;
}
if (!RescheduleTimeoutTimer(aCx)) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
}
return timerId;
}
ВАЖНОЕ ПРИМЕЧАНИЕ: Инструкция JavaScript yield
не имеет ничего общего с тем, о чем я говорю. Я говорю о sched_yield(), которая происходит, когда двоичный процесс вызывает определенные функции, такие как sched_yield()
сам, poll()
, select()
и т.д.