Chrome: tempos limite / intervalo suspensos nas guias em segundo plano?

130

Eu estava testando a precisão do setTimeoutuso deste teste . Agora notei que (como esperado) setTimeoutnão é muito preciso, mas para a maioria dos aparelhos não é dramaticamente impreciso. Agora, se eu executar o teste no Chrome e deixá-lo em uma guia em segundo plano (alternando para outra guia e navegando lá), retornando ao teste e inspecionando os resultados (se o teste terminar), eles serão alterados drasticamente. Parece que os tempos limite estiveram muito mais lentos. Testado em FF4 ou IE9, isso não ocorreu.

Parece que o Chrome suspende ou pelo menos diminui a execução do javascript em uma guia que não tem foco. Não foi possível encontrar muito na rede sobre o assunto. Isso significaria que não podemos executar tarefas em segundo plano, como, por exemplo, verificar periodicamente em um servidor usando chamadas XHR e setInterval(eu suspeito de ver o mesmo comportamento setInterval, escreveremos um teste se houver tempo comigo).

Alguém já encontrou isso? Haveria uma solução alternativa para essa suspensão / desaceleração? Você chamaria isso de bug e devo arquivá-lo como tal?

KooiInc
fonte
Interessante! Você pode dizer se o Chrome está pausando e retomando o cronômetro ou reiniciando-o depois de acessar novamente a guia? Ou o comportamento é aleatório? Poderia ter algo a ver com o fato de o Chrome executar guias em processos independentes?
HyderA
@gAMBOOKa: dê uma olhada na resposta de @ pimvdb. É provável que ocorra uma desaceleração no máximo uma vez por segundo.
KooiInc
4 anos depois e esse problema ainda existe. Eu tenho um setTimeOut para divs com a transition, portanto nem todos os divs fazem a transição ao mesmo tempo, mas na verdade 15ms um após o outro, criando algum efeito contínuo. Quando vou para outra guia e volto depois de um tempo, todos os divs são transferidos ao mesmo tempo e o setTimeOuté completamente ignorado. Não é um grande problema para o meu projeto, mas é uma adição estranha e indesejada.
Rvervuurt #
Para nossa animação que chamou setTimeout em uma sequência, a solução para nós era apenas garantir que lembrássemos o identificador / ID do timer (retornado de setTimeout) e, antes de definir um novo timer, chamaremos clearTimeout se tivermos peguei a manivela. No nosso caso, isso significa que, quando você retorna à guia, pode haver alguma estranheza inicial em termos de qual animação está sendo reproduzida, mas ela se resolve rapidamente e a animação regular é retomada. Nós pensamos que este era um problema com o código inicialmente.
Ação Dan

Respostas:

88

Recentemente, perguntei sobre isso e é comportamento por design. Quando uma guia está inativa, somente no máximo uma vez por segundo a função é chamada. Aqui está a alteração do código .

Talvez isso ajude: Como posso fazer o setInterval também funcionar quando uma guia está inativa no Chrome?

TL; DR: use Web Workers .

pimvdb
fonte
3
obrigado, eu deveria ter olhado com 'guia inativo'. Não ser um falante nativo de inglês às vezes é uma desvantagem.
KooiInc
1
@Kooilnc: Não tem problema :) Também não sou um falante nativo de inglês.
Pimvdb 17/05
22

Existe uma solução para usar Web Workers, porque eles são executados em processo separado e não são mais lentos

Eu escrevi um pequeno script que pode ser usado sem alterações no seu código - ele simplesmente substitui as funções setTimeout, clearTimeout, setInterval, clearInterval

Basta incluí-lo antes de todo o seu código

http://github.com/turuslan/HackTimer

Ruslan Tushov
fonte
7
Isso é legal e esteja ciente de que: 1. Os trabalhadores não têm acesso ao DOM, 2. Os trabalhadores só são executados se estiverem em um arquivo por conta própria. É não um substituto para setTimeout para um monte de casos.
Ghost de Madara
1
Você está certo, mas alguns navegadores modernos permitem utilizar trabalhadores sem os seus próprios arquivos usando Blobs ( html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers )
Ruslan Tushov
1
Mesmo com isso, os Web Workers estão perdendo muitas funcionalidades (a saber, DOM) que lhes permitem ser um substituto seguro para setTimeout e co.
Fantasma de Madara
E o código que precisa ser executado no front end, por exemplo, tarefas pesadas de processamento de gráficos que gostaríamos de concluir enquanto fazemos outras coisas?
Michael
Bem, você pode criar Trabalhadores, prestadores de serviços e usar a API do canvas usando um URL de dados. new Worker('data:text/javascript,(' + function myWorkerCode () { /*...*/ } + '()'). Também é uma boa maneira de verificar se você possui suporte à expressão de importação:try { eval('import("data:text/javascript,void 0")') } catch (e) { /* no support! */ }
Fábio Santos
9

Tocar um som ~ vazio força o navegador a manter o desempenho - eu o descobri depois de ler este comentário: Como fazer o JavaScript ser executado na velocidade normal no Chrome, mesmo quando a guia não está ativa?

Preciso de desempenho ilimitado sob demanda para um jogo de navegador que usa o WebSockets, por isso sei por experiência própria que o uso do WebSockets não garante desempenho ilimitado, mas a partir de testes, a reprodução de um arquivo de áudio parece garantir

Aqui estão dois loops de áudio vazios que criei para esse fim. Você pode usá-los livremente, comercialmente: http://adventure.land/sounds/loops/empty_loop_for_js_performance.ogg http://adventure.land/sounds/loops/empty_loop_for_js_performance.wav

(Eles incluem ruído de -58db, -60db não funciona)

Eu os reproduzo, sob demanda do usuário, com o Howler.js: https://github.com/goldfire/howler.js

function performance_trick()
{
    if(sounds.empty) return sounds.empty.play();
    sounds.empty = new Howl({
        src: ['/sounds/loops/empty_loop_for_js_performance.ogg','/sounds/loops/empty_loop_for_js_performance.wav'],
        volume:0.5,
        autoplay: true, loop: true,
    });
}

É triste que não exista um método interno para ativar / desativar o desempenho completo do javascript por padrão; no entanto, os mineradores de criptografia podem seqüestrar todos os seus threads de computação usando Web Workers sem nenhum aviso: |

Kaan Soral
fonte
Obrigado, 58dB é muito audível com fones de ouvido tho, mas silenciando os resolve local esse problema
Kaan Soral
1

Eu liberei trabalhador de intervalo de pacote npm que setInterval e clearInterval implementação com o uso da Web de trabalho para manter em funcionamento em abas inativas para o Chrome, Firefox e IE.

A maioria dos navegadores modernos (Chrome, Firefox e IE), intervalos (temporizadores de janelas) são fixados para disparar não mais que uma vez por segundo em guias inativas.

Você pode encontrar mais informações sobre

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Timeouts_and_intervals

gorkemcnr
fonte
0

Atualizei meu núcleo do jQuery para a versão 1.9.1 e resolveu a discrepância do intervalo nas guias inativas. Eu tentaria isso primeiro e depois procuraria outras opções de substituição de código.

Carey Estes
fonte
de qual versão você atualizou? Eu experimentei alguns problemas de tempo limite (Galeria sliders) com versão ~ 1,6
dmi3y
0

aqui está minha solução que obtém o milissegundo atual e a compara com o milissegundo em que a função foi criada. por intervalo, ele atualizará o milissegundo quando executar a função. você também pode pegar o intervalo / tempo limite por um ID.

<script>

var nowMillisTimeout = [];
var timeout = [];
var nowMillisInterval = [];
var interval = [];

function getCurrentMillis(){
    var d = new Date();
    var now = d.getHours()+""+d.getMinutes()+""+d.getSeconds()+""+d.getMilliseconds();
    return now;
}

function setAccurateTimeout(callbackfunction, millis, id=0){
    nowMillisTimeout[id] = getCurrentMillis();
    timeout[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisTimeout[id] + +millis)){callbackfunction.call(); clearInterval(timeout[id]);} }, 10);
}

function setAccurateInterval(callbackfunction, millis, id=0){
    nowMillisInterval[id] = getCurrentMillis();
    interval[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisInterval[id] + +millis)){callbackfunction.call(); nowMillisInterval[id] = getCurrentMillis();} }, 10);
}

//usage
setAccurateTimeout(function(){ console.log('test timeout'); }, 1000, 1);

setAccurateInterval(function(){ console.log('test interval'); }, 1000, 1);

</script>
SwiftNinjaPro
fonte