基于浏览器的代码在Firefox中工作时间很短(适用于Internet Explorer / Chrome)?

我有以下代码,它演示了直接从事件触发器调用长时间运行的函数与使用setTimeout()的区别。

意图行为:

  • 当按下第一个按钮时,它显示为按下,计算运行几秒钟,然后当计算结束时,按钮再次出现按下,第二列从“尚未计算”变为“已完成计算”。 (我不会详细说明为什么会出现这种情况,这是用相关的答案解释的。)

  • 当按下第二个按钮时,按钮立即按下; 第二列立即变为“计算...”文本。 当计算完成几秒钟后,第二列从“计算...”变为“计算完成”。

  • 究竟发生了什么:

  • 这在Chrome中完美运行(两个按钮的行为与预期相同)

  • 这在Internet Explorer 8中完美工作

  • 这在Firefox(v.25)中不起作用。 具体来说,第二个按钮的行为是第一个按钮的100%。

  • setTimeout()的超时从0更改为1不起作用

  • 改变超时setTimeout()0500 的作品

  • 这给我留下了一个很大的难题。

    根据setTimeout()工作原理背后的全部原因,而缺少一个原因,延迟对工作方式应该没有影响, 因为setTimeout()的主要目的是在这里改变排队顺序,而不是延迟

    那么,为什么它不能在Firefox上使用延迟0或1,但延迟500(并且在Internet Explorer 8 / Chrome上有任何延迟)可以正常工作?

    更新:除了下面的源代码,我还做了一个JSFiddle。 但由于某种原因,JSFiddle甚至拒绝在我的Internet Explorer 8上加载,所以对于该测试,下面的代码是必需的。

    UPDATE2:有人提出在Firefox中配置设置dom.min_timeout_value存在问题。 我将它从4编辑为0,重新启动浏览器,并且没有任何修复。 它仍然以0或1的超时失败,并以500成功。


    这里是我的源代码 - 我简单地将它保存到C:驱动器上的HTML文件并在所有三种浏览器中打开:

    <html><body>
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    
    <table border=1>
        <tr><td><button id='do'>Do long calc - bad status!</button></td>
            <td><div id='status'>Not Calculating yet.</div></td></tr>
        <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
            <td><div id='status_ok'>Not Calculating yet.</div></td></tr>
    </table>
    
    <script>
    function long_running(status_div) {
        var result = 0;
        for (var i = 0; i < 1000; i++) {
            for (var j = 0; j < 700; j++) {
                for (var k = 0; k < 200; k++) {
                    result = result + i + j + k;
                }
            }
        }
        $(status_div).text('calclation done');
    }
    
    // Assign events to buttons
    $('#do').on('click', function () {
        $('#status').text('calculating....');
        long_running('#status');
    });
    $('#do_ok').on('click', function () {
        $('#status_ok').text('calculating....');
        window.setTimeout(function (){ long_running('#status_ok') }, 0);
    });
    </script>
    </body></html>
    

    为了测试,您需要将Internet Explorer 8的嵌套循环边界更改为300/100/100; 或1000/1000/500的Chrome浏览器,这是由于“此JS花费的时间太长”的错误以及JavaScript引擎速度的不同。


    在Ubuntu中有一个当前(2016年6月28日)实现window.setTimeout()的副本。

    正如我们所看到的,计时器被这行代码插入:

      nsAutoPtr<TimeoutInfo>* insertedInfo =
        mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
    

    然后在你下面的几行有一个if()语句:

    if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
    ...
    

    insertedInfo == mTimeouts.Elements()检查刚插入的计时器是否超时。 下面的程序块不会执行附加的功能,但主循环会立即注意到定时器超时,因此它将跳过您期望的空闲状态(CPU的良率)。

    这显然(至少对我来说)解释了你正在经历的行为。 屏幕上的渲染是另一个进程(任务/线程),并且需要放弃该CPU来处理该另一个进程以获得重画屏幕的机会。 为了实现这一点,您需要等待足够长的时间,以便您的计时器功能不会立即执行并发生收益。

    正如你已经注意到一个500ms的停顿是有用的。 您可以使用较小的数字,例如50毫秒。 无论哪种方式,它都不能保证产量的发生,但是如果运行该代码的计算机当前没有被淹没(即,防病毒目前没有在后台全速运行,那么很可能会发生这种情况。 )

    Firefox的完整SetTimeout()函数:

    (源文件的位置: 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's 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=%sn",
                          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()等。


    我在Firefox中遇到了这个问题,同时使用jQuery来切换CSS类来控制CSS转换。

    将setTimeout的持续时间从0增加到50有帮助,但正如Alexis所说,这不是100%可靠的。

    我发现最好的(如果是长时间的)解决方案是将间隔计时器与IF语句结合起来,以在触发转换之前实际检查是否应用了必要的样式,而不是使用setTimeout并假定执行是按照预定的顺序进行的,例如

        var firefox_pause = setInterval(function() {
                //Test whether page is ready for next step - in this case the div must have a max height applied
            if ($('div').css('max-height') != "none") {
                clear_firefox_pause();
                //Add next step in queue here
            }
        }, 10);
    
        function clear_firefox_pause() {
            clearInterval(firefox_pause);
        }
    

    至少在我看来,这似乎每次都在Firefox中工作。


    在Firefox中,setTimeout()调用的最小值是可配置的,当前版本的默认值为4:

    dom.min_timeout_value window.setTimeout()函数可以为其设置超时延迟的最小时间长度(以毫秒为单位)。 这默认为4毫秒(10毫秒之前)。 调用setTimeout()的延迟小于这个值将被钳位到这个最小值。

    像0或1这样的值应该表现得像4-no想法,如果这会导致您的代码延迟或只是打破它。

    链接地址: http://www.djcxy.com/p/59025.html

    上一篇: based code work in Firefox with a small timeout (works in Internet Explorer/Chrome)?

    下一篇: date minus seconds always returns same