防止浏览器冻结和崩溃以进行长时间计算

时间:2016-09-17 22:47:43

标签: javascript freeze web-worker

我需要检查重复的数据库名称并更改此名称以避免重复。我使用@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.
    }

所以脚本工作正常,但如果我有多达100个条目。但如果我有几千条记录,浏览器就会冻结。 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资源管理器也崩溃了。怎么了? 我的记录显示在HTML表格中。

有些人建议使用网络工作者。也许有人在这里练习并有一个有效的例子吗?

2 个答案:

答案 0 :(得分:0)

这是一个解决方案,使用OODK-JS通过网络工作者计算1.000.000个数组的总和。

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

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

});

答案 1 :(得分:0)

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

根据webworkers documentation,网络工作者有其局限性,其中一个是DOM操作。所以我放弃了那个选项。

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

  1. 改进您的eliminateDuplicates算法(加快速度)。
  2. 使eliminateDuplicates异步:将元素集划分为较小的元素,并在不同的事件循环tick(setTimeout)中执行每个计算。
  3. 在这里,我向您介绍我提出的解决方案。希望它能为您提供一些想法并帮助您解决问题。

    首先,我调整了一点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来测试它。