簡體   English   中英

Javascript:setTimeout和界面凍結

[英]Javascript : setTimeout and interface freezing

上下文

我有大約10個復雜的圖表,每個圖表需要5秒才能刷新。 如果我在這10個圖表上進行循環,則刷新大約需要50秒。 在這50秒期間,用戶可以移動滾動條。 如果移動滾動條,則必須停止刷新,當滾動條停止移動時,再次進行刷新。

我在循環中使用setTimeout函數讓界面刷新。 算法是:

  • 渲染第一個圖
  • setTimeout(渲染第二個圖形,200)
  • 渲染第二個圖形時,在200ms內渲染第三個圖形,依此類推

setTimeout允許我們捕獲滾動條事件並清除下次刷新的時間,以避免在移動滾動條之前等待50秒...

問題是它不會隨時運行。

以下簡單的代碼(您可以在這個小提琴中試一試: http//jsfiddle.net/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;

    complexAlgorithm(1000);    
    clicked = true;   
}

// simulate a complexe algorithm which takes some time to execute (the graph drawing)
function complexAlgorithm(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() {complexAlgorithm(1000);}, 1);
}

代碼確實:

  • 當您將鼠標移動到紅色div時,它會更新計數器
  • 當你點擊紅色div時,它會模擬1秒的大處理(所以它因javascript單線程而凍結界面)
  • 凍結后,等待1ms,然后重新處理處理,依此類推,直到鼠標再次移動
  • 當鼠標再次移動時,它會中斷超時以避免無限循環。

問題

當您單擊一次並在凍結期間移動鼠標時,我認為將在setTimeout發生時執行的下一個代碼是mousemove事件的代碼(因此它將取消超時和凍結)但有時由於mouvemove事件,點擊計數器獲得2點或更多點而不是僅獲得1點...

結束此測試:setTimeout函數並不總是釋放資源以在mousemove事件期間執行代碼,但有時保留線程並在執行另一個代碼之前執行settimeout回調內的代碼。

這樣做的影響是,在我們的實際示例中,用戶可以等待10秒(呈現2個圖形),而不是在使用滾動條之前等待5秒。 這非常煩人,我們需要避免這種情況,並確保在渲染階段移動滾動條時只渲染一個圖形(以及其他取消)。

鼠標移動時如何確保中斷超時?

PS:在下面的簡單示例中,如果您使用200ms更新超時,則所有運行都完美但不是可接受的解決方案(真正的問題更復雜,並且200ms計時器和復雜接口會出現問題)。 請不要提供“優化圖形渲染”的解決方案,這不是問題所在。

編輯:cernunnos對問題有一個更好的解釋:另外,通過“阻塞”循環上的進程,你確保在循環完成之前不能處理任何事件,因此任何事件只會被處理(並且超時被清除)每個循環的執行(因此有時你需要在中斷之前等待2次或更多次完全執行)

這個問題完全用粗體字表示:我想確保在我想要的時候中斷執行,而不是在中斷之前等待2次或更多次完全執行


第二次編輯:

總結:采取這個jsfiddle: http//jsfiddle.net/BwNca/5/ (上面的代碼)。

更新此jsfiddle並提供以下解決方案:

鼠標在紅色div上移動。 然后單擊並繼續移動:右側計數器必須僅提升一次。 但有時它會在第一個計數器再次運行之前提高2到3次......這就是問題,它必須只提升一次!

這里的BIG問題是setTimeout一旦啟動就無法預測,特別是當它正在進行一些繁重的生命時。

你可以在這里看到演示: http//jsfiddle.net/wao20/C9WBg/

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

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

您可以采取兩項措施來最大限度地降低對瀏覽器性能的影響。

存儲setTimeoutID的所有內容,並在您想要停止時循環遍歷它

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());
}

當您嘗試停止進程並在工作線程內檢查該標志時設置一個標志,以防clearTimeout無法停止計時器

// 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;
        }       

        // ...  
    }
}

你可以嘗試這個新腳本。 http://jsfiddle.net/wao20/7PPpS/4/

我可能已經理解了這個問題,但假設你試圖在點擊一下至少1秒后阻止界面並通過移動鼠標解鎖它(在最小1秒之后):

這不是一個很好的睡眠實現,因為你一直在保持進程運行(什么也不做!=休眠),這會浪費資源。

為什么不創建一個疊加(半/完全透明的div),將它放在接口的其余部分(固定位置,全寬和全高)之上,並使用它來防止與底層接口的任何交互。 然后在條件合適時(一秒鍾過去並且用戶移動鼠標)將其銷毀。

這表現得更像睡眠(有一些初始處理時間,但隨后釋放處理器一段時間)並且應該幫助您實現所需的行為(假設我理解正確)。

它還有一個額外的好處,就是允許你給用戶一些正在進行某些處理的視覺提示。

編輯:此外,通過“阻塞”循環上的進程,您確保在該循環完成之前不能處理任何事件,因此任何事件只會在每個循環的執行之間處理(並且超時清除)(因此為什么您有時必須等待2次或更多次完全執行才能中斷)。

令人驚訝的是,當你setTimeout()時,你還沒有想到這一點; 你可以在那之后輸入支票。 變量為true然后廢棄等待,或者將其廢棄。 現在有一種方法可以檢查滾動條滾動。 在使用平均值在變量內檢查它之后,您將發現這將在滾動條形圖時重復無效次數,使許多執行時間為5秒。 要解決此問題,請添加1秒等待,以確保它不會重復。 別客氣 :)

任何長時間運行的功能都會占用您的瀏覽器窗口。 考慮使用WebWorkers在您的主JavaScript代碼之外移動complexAlgorithm()。

答案就在你的問題中

...刷新必須停止,當滾動條停止移動時,刷新再次發生。

您應該以這樣的方式編寫complexAlgorithm,這樣您幾乎可以立即在中間制動它(就在您知道必須重新運行時)

所以主要代碼看起來應該是這樣的

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

並在setTimeout中以小塊(每個運行<1秒)渲染圖形

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);
}

這只是一個想法草圖,真正的實現可以完全不同

如果沒有網絡工作人員,你想做的事情是不可能完成的,這只是在一些最新的瀏覽器中實現的,特別是Chrome。

否則,您必須在隊列中中斷算法。 就像jQuery UI將每個下一個動畫計算放入隊列中一樣。 http://api.jquery.com/jQuery.queue/

它是一個簡單的隊列,下一個指令集在setTimeout的幫助下排隊。

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

可以翻譯成

 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);
 }) ;

這只是一個示例,說明如何使用較小的獨立迭代異步執行同步for循環來執行相同的邏輯。

嘗試檢查網絡工作者。 我認為在這種情況下它會很有用。

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM