高量级任务执行优化

62 阅读5分钟

高量级任务执行优化

github地址

宏任务的高并发

在一个页面中,如果有大量的任务需要执行,那么就会造成页面的卡顿,这时候就需要对任务进行优化,让任务分散到不同的时间段执行,这样就不会造成页面的卡顿。

首先是页面的渲染逻辑:也就是事件循环, !事件循环 所以在整个的优化区间是在宏任务中,微任务会在整个微队列清空之后,才会执行下一个宏任务。所以在宏任务中,我们可以将任务分散到不同的时间段执行,这样就不会造成页面的卡顿。

// worker是一个独立的线程,可以将一些耗时的任务放到worker中执行,这样就不会阻塞主线程,从而提高页面的性能
// worker中不能操作DOM,所以需要将数据传递给主线程,然后再由主线程操作DOM
// worker中的代码必须是独立的文件,不能在当前文件中写
// worker中的代码必须是纯净的,不能操作DOM,也不能操作window对象 和 document对象(self对象除外)
// worker.js
self.addEventListener('message', function(e) {
    if (e.data === 'start') {
        // 执行耗时任务
        let start = Date.now();
        let result = performHeavyTask();
        let end = Date.now();
        self.postMessage('耗时任务执行完毕,结果为:' + result + ',耗时:' + (end - start) + 'ms');
    }
});

function performHeavyTask() {
    // 这里是耗时任务的代码
    // 例如,进行复杂计算或处理大量数据
    let sum = 0;
    for (let i = 0; i < 1000000000; i++) {
        sum += i;
    }
    return sum;
}
// main
 // 主线程中的代码
    document.getElementById('startButton').addEventListener('click', function() {
        if (window.Worker) {
            const myWorker = new Worker('worker.js');

            myWorker.postMessage('start');

            myWorker.onmessage = function(e) {
                console.log('任务完成:', e.data);
            };
        } else {
            console.log('您的浏览器不支持 Web Workers。');
        }
    });
  • 方案二:requestAnimationFrame requestAnimationFrame是浏览器提供的一个API,可以将任务分散到不同的时间段执行,从而提高页面的性能 requestAnimationFrame的回调函数会在浏览器下一次重绘之前执行,所以可以将任务分散到不同的时间段执行 requestAnimationFrame的回调函数中的this指向window对象 requestAnimationFrame的回调函数中的参数是一个时间戳,表示requestAnimationFrame开始去执行回调函数的时间
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>requestAnimationFrame 任务优化</title>
    <style>
        .spinner {
            width: 40px;
            height: 40px;
            background-color: #333;
            margin: 100px auto;
            animation: spin 1s infinite linear;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="spinner"></div>
    <button id="startButton">执行耗时任务</button>

    <script>
        document.getElementById('startButton').addEventListener('click', function() {
            let count = 0;
            const maxCount = 1000000; // 假设任务是计数到一百万

            function performTask() {
                const start = performance.now();
                while (performance.now() - start < 16) {
                    if (count >= maxCount) {
                        console.log('任务完成');
                        return;
                    }
                    count++;
                }
                requestAnimationFrame(performTask);
            }

            requestAnimationFrame(performTask);
        });
    </script>
</body>
</html>
  • 方案三:requestIdleCallback
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>requestIdleCallback 任务优化</title>
    <style>
        .spinner {
            width: 40px;
            height: 40px;
            background-color: #333;
            margin: 100px auto;
            animation: spin 1s infinite linear;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="spinner"></div>
    <button id="startButton">执行耗时任务</button>

    <script>
        document.getElementById('startButton').addEventListener('click', function() {
            let count = 0;
            const maxCount = 1000000; // 假设任务是计数到一百万

            function performIdleTask(deadline) {
                while (count < maxCount && deadline.timeRemaining() > 0) {
                    count++;
                }

                if (count < maxCount) {
                    requestIdleCallback(performIdleTask);
                } else {
                    console.log('任务完成');
                }
            }

            requestIdleCallback(performIdleTask);
        });
    </script>
</body>
</html>

  • 方案四:Generators
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Generator 任务优化</title>
    <style>
        .spinner {
            width: 40px;
            height: 40px;
            background-color: #333;
            margin: 100px auto;
            animation: spin 1s infinite linear;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="spinner"></div>
    <button id="startButton">执行耗时任务</button>

    <script>
        function* taskGenerator(maxCount) {
            let count = 0;
            while (count < maxCount) {
                yield count++;
            }
        }

        document.getElementById('startButton').addEventListener('click', function() {
            const maxCount = 1000000; // 假设任务是计数到一百万
            const gen = taskGenerator(maxCount);

            function runTask() {
                const result = gen.next();
                if (!result.done) {
                    setTimeout(runTask, 0);
                } else {
                    console.log('任务完成');
                }
            }

            runTask();
        });
    </script>
</body>
</html>
微任务的高并发

对于微任务的高并发,重要的便是不同任务的执行时间不同,将api的调用和返回结果一一对应是最好的,这样就不会造成页面的卡顿。

function createAsyncTaskQueue() {
    let tasks = [];
    let isTaskRunning = false;

    function enqueue(task) {
        tasks.push(task);
        if (!isTaskRunning) {
            isTaskRunning = true;
            runNextTask();
        }
    }

    function runNextTask() {
        if (tasks.length === 0) {
            isTaskRunning = false;
            return;
        }

        const task = tasks.shift();
        task().then(() => {
            runNextTask();
        });
    }

    return { enqueue };
}
// vue中
<template>
    <div>
        <h1>{{ message }}</h1>
        <button @click="testQueue">Test Async Queue</button>
</div>
</template>

<script >
    import { ref } from 'vue';

    export default {
    setup() {
    const message = ref('Ready to test async queue');

    const asyncQueue = createAsyncTaskQueue();

    const testQueue = () => {
    message.value = 'Starting async tasks...';

    asyncQueue.enqueue(() => delayTask(1000, 'Task 1 completed'));
    asyncQueue.enqueue(() => delayTask(2000, 'Task 2 completed'));
    asyncQueue.enqueue(() => delayTask(1500, 'Task 3 completed'));
};

    function delayTask(delay, result) {
    return new Promise(resolve => {
    setTimeout(() => {
    message.value = result;
    resolve();
}, delay);
});
}

    return {
    message,
    testQueue
};
}
}
</script>

<style scoped>
    h1 {
    color: #42b983;
}
</style>

这种方式,通过将api的调用和执行进行绑定,但是这样回丢失掉并发,得不偿失。也可以通过生成对应的时间戳等等,但是也可以直接将then一起传过来,来解决不一致的问题,同时也可以通过控制请求的数量来控制并发的数量。

function createAsyncTaskQueue(maxConcurrency) {
    let tasks = [];
    let currentRunning = 0;
    let taskCounter = 0;

    function enqueue(task) {
        const taskId = taskCounter++;
        return new Promise((resolve, reject) => {
            tasks.push({ task, resolve, reject, taskId });
            processTasks();
        });
    }

    function processTasks() {
        while (currentRunning < maxConcurrency && tasks.length > 0) {
            currentRunning++;
            const { task, resolve, reject, taskId } = tasks.shift();
            task().then(result => {
                resolve(result);
                currentRunning--;
                processTasks();
            }).catch(error => {
                reject(error);
                currentRunning--;
                processTasks();
            });
        }
    }

    return { enqueue };
}

// 使用队列,最多同时运行3个异步任务
const asyncQueue = createAsyncTaskQueue(3);
// vue
<template>
    <div>
        <button @click="testQueue">Test Async Queue</button>
    <p v-for="(result, index) in results" :key="index">{{ result }}</p>
</div>
</template>

<script>
    import { ref } from 'vue';
    function createAsyncTaskQueue(maxConcurrency) {
    let tasks = [];
    let currentRunning = 0;
    let taskCounter = 0;

    function enqueue(task) {
    const taskId = taskCounter++;
    return new Promise((resolve, reject) => {
    tasks.push({ task, resolve, reject, taskId });
    processTasks();
});
}

    function processTasks() {
    while (currentRunning < maxConcurrency && tasks.length > 0) {
    currentRunning++;
    const { task, resolve, reject, taskId } = tasks.shift();
    task().then(result => {
    resolve(result);
    currentRunning--;
    processTasks();
}).catch(error => {
    reject(error);
    currentRunning--;
    processTasks();
});
}
}

    return { enqueue };
}


    export default {
    setup() {
    const results = ref([]);

    const asyncQueue = createAsyncTaskQueue(3);

    const testQueue = () => {
    results.value = []; // 清空之前的结果

    asyncQueue.enqueue(() => delayTask(1000, 'Task 1 completed').then((res) => { console.log(res) }));
    asyncQueue.enqueue(() => delayTask(2000, 'Task 2 completed').then((res) => { console.log(res) }));
    asyncQueue.enqueue(() => delayTask(1500, 'Task 3 completed').then((res) => { console.log(res) }));
};

    function delayTask(delay, result) {
    return new Promise(resolve => {
    setTimeout(() => {
    // results.value.push(result); // 将结果添加到数组
    resolve(result);
}, delay);
});
}

    return {
    results,
    testQueue
};
}
}
</script>

<style scoped>
    /* 可以在这里添加样式 */
</style>