【问题标题】:Prevent browser freezing and crashing for long time calculation防止浏览器冻结和长时间计算崩溃
【发布时间】:2016-09-30 19:16:01
【问题描述】:

我需要检查重复的数据库名称并更改此名称以避免重复。我使用@Jefré N 建议的脚本。

function eliminateDuplicates() {

    var repeats = {};
    var error = false;

    //cache inputs
    var $inputs = $("input[type='text']");

    //loop through inputs and update repeats
    for (i = 0; i < $inputs.length; ++i) {
        //cache current element
        var cur = $inputs[i];

        //remove class
        $(cur).removeClass("double-error");

        //get text of this element
        var text = $(cur).val();

        //no text -- continue
        if (text === "") {
            continue;
            }
        //first time we've came across this value -- intialize it's counter to 1
        if ((text in repeats) === false) {
            repeats[text] = 1;
            }
        //repeat offender. Increment its counter.
        else {
            repeats[text] = repeats[text] + 1;
            }

        //update the the value for this one
        $(cur).val(text + "-" + repeats[text]);
        }

    return error; // always returns false since I'm not sure
                  // when it's supposed to return true.
    }

所以脚本工作正常,但如果我有多达一百个条目。但是如果我有几千条记录,浏览器就会冻结。 Firefox 完全崩溃了。如何通过添加例如一些加载线或一些时钟指针来防止浏览器冻结和崩溃?也许我需要使用一些 setTimeout() 函数或其他东西。请帮助防止此浏览器冻结和崩溃问题。

我试过这个:

function processLargeArrayAsync(array, fn, maxTimePerChunk, context) {
    context = context || window;
    maxTimePerChunk = maxTimePerChunk || 200;
    var index = 0;

    function now() {
        return new Date().getTime();
    }

    function doChunk() {
        var startTime = now();
        while (index < array.length && (now() - startTime) <= maxTimePerChunk) {
            // callback called with args (value, index, array)
            fn.call(context, array[index], index, array);
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

-

processLargeArrayAsync(veryLargeArray, myCallback);

没有成功。 Chrome 冻结,IE11 资源管理器也是,Firefox 崩溃。怎么了? 我的记录出现在 HTML 表中。

有些人建议使用网络工作者。也许这里有人有这种做法并有一个有效的例子?

【问题讨论】:

  • setTImeout 会起作用,你的问题是什么?你试过了吗?发布您尝试过的内容,最好在 jsfiddle.net 上发布链接。
  • 好的,谢谢你的建议。新人试过这个。也许有些人有很好的工作例子?
  • 问候@KęstutisBanišauskas!我有几个问题。 1. 为什么需要做客户端? 2. 您还在访问来自&lt;input&gt;s 的值吗?如果没有,你是如何遍历你需要的值的?
  • @Jefré N. 谢谢,当页面正在加载从数据库中检索的所有数据时。比所有数据都放入输入字段以进一步检查重复项并使用脚本添加数字。因此,当记录达到数百个时,一切正常。当记录数以千计时,浏览器会冻结。并弹出警报。所以我需要一些解决方案来防止这种情况。我想也许网络工作者可以提供帮助,但这里没有经验。并且需要为此添加一些加载栏以知道后台进程正在运行。

标签: javascript freeze web-worker


【解决方案1】:

我认为代码中最麻烦的部分是 DOM 访问:获取输入值并更新它们。

根据webworkers documentation,webworkers 有其局限性,其中之一就是 DOM 操作。所以我会放弃那个选项。

为了解决问题,我会这样做:

  1. 改进您的eliminateDuplicates 算法(使其更快)。
  2. 使eliminateDuplicates 异步:将元素集划分为较小的元素,并在不同的事件循环滴答 (setTimeout) 中执行每个计算。

在这里,我向您展示我想出的解决方案。希望它能给您一些想法并帮助您解决问题。

首先,我稍微调整了一下eliminateDuplicates(我称之为modifyDOM

function modifyDOM(elements, repeats) {
    var input, text, i = 0;
    for (; i < elements.length; i++) {
        input = elements[i];
        text = input.value;
        // Remove class.
        input.className = input.className.replace(/\bdouble-error\b/, '');
        if (text) {
            repeats[text] = ~~repeats[text] + 1;
            input.value = text + "-" + repeats[text];
        }
    }
}

我避免在主循环中使用 jQuery,因为它的包装器会使事情变慢,而且在你的情况下它不值得使用它。这些微小的变化提高了每 10.000 个元素 100 毫秒的性能(给予或接受)。

我创建了两个使用modifyDOM 的函数:一个是异步的,另一个是同步的。

function parseElementsNonBlocking(elements, maxChunkSize) {
    var repeats = {},
        nChunks = Math.floor(elements/maxChunkSize),
        i = 0,
        j = 1;

    //loop through inputs and update repeats
    for(; i < nChunks; i++, j++) {
        setTimeout(modifyDOM.bind(null, elements.slice(i, j*maxChunkSize), repeats), 0);
    }
    // Rest
    setTimeout(modifyDOM.bind(null, elements.slice(i), repeats), 0);
}

function parseElementsBlocking(elements) {
    var repeats = {};

    //loop through inputs and update repeats
    modifyDOM(elements, repeats);
}

最后,为了测试一切,一个函数在 DOM 准备好时执行并创建 10.000 个输入。然后它会输出运行上述任何方法所需的时间。

$(function () {
    var inputsDiv = $('#inputs'), i, time;
    for (i = 0; i < 10000; i++) {
        var val = i % 3 === 0 ? 'Mickey' : (i % 3 === 1 ? 'Mouse' : '');
        inputsDiv.append('<input type="text" class="double-error" name="FirstName" value="' + val + '">');
    }

    time = Date.now();
    //parseElementsBlocking($("input[type='text']"));
    parseElementsNonBlocking($("input[type='text']"), 100);
    console.log(Date.now() - time);
});

这里有 fiddle 来测试它。

【讨论】:

  • 您好,感谢您的回答。好像我错过了什么。我可以让它工作。这个函数 'function modifyDOM(elements, repeats)' 对我不起作用。
【解决方案2】:

这是一个使用 OODK-JS 通过 webworkers 计算 1.000.000 个条目的数组的总和的解决方案。

此解决方案使用 SynchronizedQueue 基础类实现生产者/消费者设计模式:生产者(主线程)为数组的每个块生成一个任务并将其添加到队列中。消费者(网络工作者)在队列中接受一项任务并执行它直到没有人离开。所有任务执行完毕后,生产者显示最终结果

// main.js (producer)
          OODK.config({
            'path': {
              'oodk': '../src',
              'workspace': 'workspace'
            }
          });

          OODK(function($, _){

            $.import('{oodk}/foundation/utility/Thread', '[util.concurrent]', '{workspace}/project/Task');

            // array helper class to handle arrays
            var ArrayHelper = $.class(function($, µ, _){

              $.static(function($, µ, _){

                // slice an array into chunks using chunkLength argument
                // as delimiter 
                $.public(function slice(arr, chunkLength){

                  return arr.reduce(function(arr, val, index){

                    var chunkIndex = Math.floor(index/chunkLength); 

                    if(!arr[chunkIndex]) {
                      arr[chunkIndex] = [];
                    }

                    arr[chunkIndex].push(val);

                    return arr;
                  }, []);
                });

                // generate an array of len argument length
                // containing random values 
                $.public(function random(len){

                  var arr = [];

                  for(var i =0; i<len; i++){
                    arr.push(Math.random()*10);
                  }

                  return arr;
                })
              });

            });

            // class to handle a pool of thread
            var ThreadPool = $.class(function($, µ, _){

              // number of threads to instantiate
              $.private('num');

              // queue to works with
              $.private('queue');

              $.public(function __initialize(num, queue){

                _.num = num;

                _.queue = queue;
              });

              // start the pool
              $.public(function start(){

                // bind listeners
                var threadListener= $.new(Producer);

                for(var i=0; i<_.num; i++){

                  // instantiate consumers
                  var consumer = $.new(OODK.foundation.util.Thread, "consumer.js");

                  $.on(consumer, 'thread.ready', threadListener);

                  consumer.start();
                }

                $.on(_.queue, 'synchronizedQueue.taskDone', threadListener);

              });

            });

            // Event Listener for the thread
            var Producer = $.implements(OODK.foundation.EventListener).class(function($, µ, _){

              // number of task done
              $.private('taskDone', 0);

              // final result
              $.private('finalResult', 0);

              $.private(function __processEvent(evt){

                if(evt.getType() === 'thread.ready'){

                  // the thread is ready, synchronize the queue with the current thread
                  queue.synchronize(evt.getTarget());

                }else if(evt.getType() == 'synchronizedQueue.taskDone'){
                  //message received from the consumer that it has performed a task

                  _.taskDone++;

                  var cqueue = evt.getTarget();

                  var chunkResult = evt.getData();

                  _.finalResult += chunkResult;

                  jQuery('#chunksDone').text(_.taskDone);

                  if(cqueue.getCapacity() == _.taskDone){

                    // once all tasks are performed display the final result
                    $.log('final sum is ' + _.finalResult);
                  }else{
                    // each time a chunk is calculated display the intermediate result 
                    $.log('intermediate result ' + _.finalResult);
                  }
                }
              });
            });

            // generate a large array of 1.000.000 random values
            var myHugeArray = ArrayHelper.self.random(1000000);

            // split this array into chunks of 2500 length
            var chunks = ArrayHelper.self.slice(myHugeArray, 25000);

            // instantiate a synchronized queue setted as size the number of chunks
            var queue = $.new(OODK.foundation.util.concurrent.SynchronizedQueue, chunks.length);

            // for each chunk create a task and add it to queue
            for(var i=0; i<chunks.length; i++){

              var chunk = chunks[i];

              // create a task for each chunk of the array
              var task = OODK.project.Task.self.factory(chunk);

              // and add it to the queue
              queue.put(task);
            }

            // instantiate a pool of 2 threads working on the given queue
            var threadPool = $.new(ThreadPool, 2, queue);

            // start the pool
            threadPool.start();

            $.log('calculate the sum of an array of 1.000.000 entries using 2 threads ...');
          });

消费者(网络工作者)

//consumer.js

OODK.config({
  'path': {
    'oodk': '../src',
    'workspace': 'workspace'
  }
});

OODK(function($, _){

  // import the concurrent API package as well as the task class
  $.import('[util.concurrent]', '{workspace}/project/Task');

  // start the synchronizer
  OODK.foundation.util.concurrent.SynchronizedObject.self.start();

  // EventListener Class to handle synchronized queue events
  $.implements(OODK.foundation.EventListener).class(function Consumer($, µ, _){

    $.protected(function __processEvent(evt){

      if(evt.getType() == 'synchronizedQueue.ready'){
        //queue is synchronized

        var queue = evt.getTarget();

        // bind listener
        $.on(queue, 'synchronizedQueue.elementRetrieved', this);

        // take a task: get the heap of the stack and delete it
        queue.take();

      }else if(evt.getType() == 'synchronizedQueue.elementRetrieved'){

        // task is retrieved from the queue

        var task = evt.getData();

        var queue = evt.getTarget();

        // execute the task
        var result = task.execute();

        // notify the producer that the task is done
        queue.notify('synchronizedQueue.taskDone', result);

        if(queue.remainingElements()>0){
          // at least one task is still in the queue, take it

          queue.take();
        }

      }
    });
  });

  var threadListener = $.new(_.Consumer);

  // global listener for the synchronizedQueue.ready event 
  // triggered when the synchronzied queue is synchronized with this thread
  $.on('synchronizedQueue.ready', threadListener);

});

实现自定义逻辑的任务类

OODK('project', function($, _){

  $.public().implements(OODK.foundation.Serializable).class(function Task($, µ, _){

    // the array chunk to calculate
    $.private('chunk');

    $.public(function __initialize(chunk){
      _.chunk = chunk;
    });

    // calculate the sum of all entries of a chunk
    // implements the custom logic here
    $.public(function execute(){

      var result = 0;

      for(var i=0; i<_.chunk.length; i++){
        result += _.chunk[i];
      }

      return result;
    });

    $.static(function($, µ, _){

      $.public(function factory(chunk){

        var task = $.new($.ns.Task, chunk);

        return task;
      });
    });
  });

});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-18
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 2016-11-03
    • 1970-01-01
    • 2012-09-28
    相关资源
    最近更新 更多