te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>Javascript : setTimeout and interface freezing - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Javascript : setTimeout and interface freezing - Stack Overflow

programmeradmin3浏览0评论

Context

I've got about 10 plex graphs which take 5sec each to refresh. If I do a loop on these 10 graphs, it takes about 50 seconds to refresh. During these 50 seconds, the user can move a scrollbar. If the scrollbar is moved, the refresh must stop and when the scrollbar stops to move, the refresh occurs again.

I'm using the setTimeout function inside the loop to let the interface refresh. the algorithm is :

  • render the first graph
  • setTimeout(render the second graph, 200)
  • when the second graph is rendered, render the third one in 200ms, and so on

The setTimeout allows us to catch the scrollbar event and to clearTimeout the next refresh to avoid to wait 50sec before moving the scrollbar...

The problem is that it does not run anytime.

Take the simple following code (you can try it in this fiddle : /) :

HTML :

<div id="test" style="width: 300px;height:300px; background-color: red;">

</div>
<input type="text" id="value" />
<input type="text" id="value2" />

Javascript :

var i = 0;
var j = 0;
var timeout;
var clicked = false;

// simulate the scrollbar update : each time mouse move is equivalent to a scrollbar move
document.getElementById("test").onmousemove = function() {

    // ignore first move (because onclick send a mousemove event)
    if (clicked) {
        clicked = false;
        return;
    }

    document.getElementById("value").value = i++; 
    clearTimeout(timeout);
}

// a click simulates the drawing of the graphs
document.getElementById("test").onclick = function() {
    // ignore multiple click
    if (clicked) return;

    plexAlgorithm(1000);    
    clicked = true;   
}

// simulate a plexe algorithm which takes some time to execute (the graph drawing)
function plexAlgorithm(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }   

  document.getElementById("value2").value = j++;

  // launch the next graph drawing
  timeout =  setTimeout(function() {plexAlgorithm(1000);}, 1);
}

The code does :

  • when you move your mouse into the red div, it updates a counter
  • when you click on the red div, it simulates a big processing of 1sec (so it freezes the interface due to javascript mono thread)
  • after the freezing, wait 1ms, and resimulate the processing and so on until the mouse move again
  • when the mouse move again, it breaks the timeout to avoid infinite loop.

The problem

When you click one time and move the mouse during the freeze, I was thinking that the next code that will be executed when a setTimeout will occurs is the code of the mousemove event (and so it will cancel the timeout and the freeze) BUT sometimes the counter of click gains 2 or more points instead of gaining only 1 point due to the mouvemove event...

Conclusion of this test : the setTimeout function does not always release resource to execute a code during a mousemove event but sometimes kept the thread and execute the code inside the settimeout callback before executing another code.

The impact of this is that in our real example, the user can wait 10 sec (2 graphs are rendered) instead of waiting 5 seconds before using the scrollbar. This is very annoying and we need to avoid this and to be sure that only one graph is rendered (and other canceled) when the scrollbar is moved during a render phase.

How to be sure to break the timeout when the mouse move ?

PS: in the simple example below, if you update the timeout with 200ms, all runs perfectly but it is not an acceptable solution (the real problem is more plex and the problem occurs with a 200ms timer and a plex interface). Please do not provide a solution as "optimize the render of the graphs", this is not the problem here.

EDIT : cernunnos has a better explanation of the problem : Also, by "blocking" the process on your loop you are ensuring no event can be handled until that loop has finished, so any event will only be handled (and the timeout cleared) inbetween the execution of each loop (hence why you sometimes have to wait for 2 or more full executions before interrupting).

The problem is exactly contains in bold words : I want to be sure to interrupt the execution when I want and not to wait 2 or more full executions before interrupting


Second EDIT :

In summary : takes this jsfiddle : / (the code above).

Update this jsfiddle and provide a solution to :

Mouse move on the red div. Then click and continue moving : the right counter must raise only once. But sometimes it raises 2 or 3 times before the first counter can run again... this is the problem, it must raise only once !

Context

I've got about 10 plex graphs which take 5sec each to refresh. If I do a loop on these 10 graphs, it takes about 50 seconds to refresh. During these 50 seconds, the user can move a scrollbar. If the scrollbar is moved, the refresh must stop and when the scrollbar stops to move, the refresh occurs again.

I'm using the setTimeout function inside the loop to let the interface refresh. the algorithm is :

  • render the first graph
  • setTimeout(render the second graph, 200)
  • when the second graph is rendered, render the third one in 200ms, and so on

The setTimeout allows us to catch the scrollbar event and to clearTimeout the next refresh to avoid to wait 50sec before moving the scrollbar...

The problem is that it does not run anytime.

Take the simple following code (you can try it in this fiddle : http://jsfiddle/BwNca/5/) :

HTML :

<div id="test" style="width: 300px;height:300px; background-color: red;">

</div>
<input type="text" id="value" />
<input type="text" id="value2" />

Javascript :

var i = 0;
var j = 0;
var timeout;
var clicked = false;

// simulate the scrollbar update : each time mouse move is equivalent to a scrollbar move
document.getElementById("test").onmousemove = function() {

    // ignore first move (because onclick send a mousemove event)
    if (clicked) {
        clicked = false;
        return;
    }

    document.getElementById("value").value = i++; 
    clearTimeout(timeout);
}

// a click simulates the drawing of the graphs
document.getElementById("test").onclick = function() {
    // ignore multiple click
    if (clicked) return;

    plexAlgorithm(1000);    
    clicked = true;   
}

// simulate a plexe algorithm which takes some time to execute (the graph drawing)
function plexAlgorithm(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }   

  document.getElementById("value2").value = j++;

  // launch the next graph drawing
  timeout =  setTimeout(function() {plexAlgorithm(1000);}, 1);
}

The code does :

  • when you move your mouse into the red div, it updates a counter
  • when you click on the red div, it simulates a big processing of 1sec (so it freezes the interface due to javascript mono thread)
  • after the freezing, wait 1ms, and resimulate the processing and so on until the mouse move again
  • when the mouse move again, it breaks the timeout to avoid infinite loop.

The problem

When you click one time and move the mouse during the freeze, I was thinking that the next code that will be executed when a setTimeout will occurs is the code of the mousemove event (and so it will cancel the timeout and the freeze) BUT sometimes the counter of click gains 2 or more points instead of gaining only 1 point due to the mouvemove event...

Conclusion of this test : the setTimeout function does not always release resource to execute a code during a mousemove event but sometimes kept the thread and execute the code inside the settimeout callback before executing another code.

The impact of this is that in our real example, the user can wait 10 sec (2 graphs are rendered) instead of waiting 5 seconds before using the scrollbar. This is very annoying and we need to avoid this and to be sure that only one graph is rendered (and other canceled) when the scrollbar is moved during a render phase.

How to be sure to break the timeout when the mouse move ?

PS: in the simple example below, if you update the timeout with 200ms, all runs perfectly but it is not an acceptable solution (the real problem is more plex and the problem occurs with a 200ms timer and a plex interface). Please do not provide a solution as "optimize the render of the graphs", this is not the problem here.

EDIT : cernunnos has a better explanation of the problem : Also, by "blocking" the process on your loop you are ensuring no event can be handled until that loop has finished, so any event will only be handled (and the timeout cleared) inbetween the execution of each loop (hence why you sometimes have to wait for 2 or more full executions before interrupting).

The problem is exactly contains in bold words : I want to be sure to interrupt the execution when I want and not to wait 2 or more full executions before interrupting


Second EDIT :

In summary : takes this jsfiddle : http://jsfiddle/BwNca/5/ (the code above).

Update this jsfiddle and provide a solution to :

Mouse move on the red div. Then click and continue moving : the right counter must raise only once. But sometimes it raises 2 or 3 times before the first counter can run again... this is the problem, it must raise only once !

Share Improve this question edited Apr 22, 2013 at 10:09 Jerome Cance asked Apr 12, 2013 at 13:50 Jerome CanceJerome Cance 8,18312 gold badges55 silver badges107 bronze badges 7
  • 3 The problem has nothing to do with the setTimeout. The problem is the fact that you are running a large loop. You can not make a sleep method with a loop, it will lock up the browser. – epascarello Commented Apr 12, 2013 at 13:56
  • The only loop I see in this code is to waste 1sec time to freeze the interface (it simulates a more plex algorithm that I can't copy here), this isn't the problem I think – Jerome Cance Commented Apr 12, 2013 at 13:57
  • Events queued when you're purposely freezing the browser will fire before timeout = setTimeout(function() {sleep(1000);}, 1);, meaning that clearTimeout(timeout); will be run before the timeout is set. Further, why do you want to make the browser freeze? This is not good practice as it can effect more than just current tab. – Paul S. Commented Apr 12, 2013 at 13:58
  • 4 Well, I don't think jerome wants to make the browser freeze. It's not the point, it's only for test purpose. – Guian Commented Apr 12, 2013 at 14:06
  • I don't think you can force the event loop to favor certain events - their asynchronity is out of the script's control. For example in Opera I cannot reproduce your problem (though I can see it). – Bergi Commented Apr 12, 2013 at 15:41
 |  Show 2 more ments

7 Answers 7

Reset to default 2

The BIG problem here is setTimeout is unpredictable once it started, and especially when it is doing some heavy lifiting.

You can see the demo here: http://jsfiddle/wao20/C9WBg/

var secTmr = setTimeout(function(){
    $('#display').append('Timeout Cleared > ');
    clearTimeout(secTmr);        

    // this will always shown
    $('#display').append('I\'m still here! ');
}, 100);

There are two things you can do to minimize the impact on the browser performance.

Store all the intances of the setTimeoutID, and loop through it when you want to stop

var timers = []

// When start the worker thread
timers.push( setTimeout(function () { sleep(1000);}, 1) );

// When you try to clear 
while (timers.length > 0) {
     clearTimeout(timers.pop());
}

Set a flag when you try to stop process and check that flag inside your worker thread just in case clearTimeout failed to stop the timer

// Your flag 
var STOPForTheLoveOfGod = false;

// When you try to stop 
STOPForTheLoveOfGod = true; 
while (timers.length > 0) {       
     clearTimeout(timers.pop()); 
}

// Inside the for loop in the sleep function 
function sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
        if (STOPForTheLoveOfGod) {
            break;
        }       

        // ...  
    }
}

You can try out this new script. http://jsfiddle/wao20/7PPpS/4/

I may have understood the problem but assuming you are trying to block the interface after a click for a minimum of 1 second and unblocking it by moving the mouse (after that 1 second minimum):

This is not a good implementation of sleep, as you are keeping the process running the whole time (doing nothing != sleeping), this results in a waste of resources.

Why not create an overlay (a semi/fully transparent div), put it on top of the rest of the interface (position fixed, full width and full height) and use it to prevent any interaction with the underlying interface. Then destroy it when the conditions are right (a second has passed and the user moved the mouse).

This behaves more like a sleep (has some initial processing time but then releases the processor for a given amount of time) and should help you achieve the behavior you need (assuming i understood it right).

It has the added bonus of allowing you to give the user some visual cue that some processing is being done.

Edit: Also, by "blocking" the process on your loop you are ensuring no event can be handled until that loop has finished, so any event will only be handled (and the timeout cleared) inbetween the execution of each loop (hence why you sometimes have to wait for 2 or more full executions before interrupting).

Surprising enough you have not figured out that, when you setTimeout(); you can input a check after that. A variable is true then trash the wait, or trash it. Now there is a method that you can check to scroll with a scroll bar. After you have checked it true inside a variabled using the means, then you will find this will repeat inifite times as they scroll the bar, making many executing times of 5 seconds. To fix this add a 1 second wait to make sure it doesn't over repeat. Your wele :)

Any long-running function is going to tie up your browser window. Consider moving your plexAlgorithm() outside of your main javascript code using WebWorkers.

The answer is in your question

...the refresh must stop and when the scrollbar stops to move, the refresh occurs again.

You should write plexAlgorithm in such way that you can almost instantly brake it in a middle (just when you know you will have to re run)

so main code should look something like

stopAllRefresh;  //should instantly(or after pleting small chunk) stop refresh
setTimeout(startRefresh, 100);

and render graph in small chunks (each runs < 1sec) in setTimeout like

var curentGraph = 0;
var curentChunk = 0;

function renderGraphChunk(){
    if (needToBreak)  //check if break rendering
    {exit};
// Render chunk here
    render(curentGraph, curentChunk);
    curentChunk +=1;

    setTimeout(renderGraphChunk, 1);
}

this is just a idea sketch, real implementation can be pletely different

What you want to do can not be done without web worker, that is only implemented in some latest browser specially Chrome.

Otherwise, you have to break your algorithm in queue. Just like jQuery UI puts every next animation calculation in queue. http://api.jquery./jQuery.queue/

It is a simple queue and next instruction set is queued with help of setTimeout.

 for (i=0; i <1000; i++)
 {
     process (i) ;
 }

Can be translated to

 function queue(s,n, f)
 {
    this.i=s;
    this.n=n;
    this.f=f;
    this.step = function(){
       if ( this.i <this.n)
       { 
            this.f(this.i);
            this.i = this.i +1;
            var t = this;
            setTimeout( function ( ) { t.step(); } , 5);
       }
    }
    this.step();

 }


 queue ( O, 1000, function(i){
     process(i);
 }) ;

This is just an example of how Synchronous for loop can be written to execute same logic asynchronously using smaller independent iteration.

Try and check out web workers. I think it will be useful in this situation.

  1. http://en.wikipedia/wiki/Web_worker

  2. http://www.html5rocks./en/tutorials/workers/basics/

发布评论

评论列表(0)

  1. 暂无评论