You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript抢占式后台任务调度通用方案及IndexedDB加载场景问询

实现抢占式后台任务调度方案

要解决这个问题,核心是让Web Worker支持任务优先级调整和正在执行任务的中断,同时主线程和Worker保持状态同步。下面是一套完整的JavaScript实现方案,分主线程和Web Worker两部分来说:

一、主线程逻辑:缓存管理与用户交互

主线程负责维护缓存状态、响应用户交互,以及和Worker的通信。我们用Map来存储缓存和加载状态,确保快速查询:

// 主线程:缓存存储,key是表名,value是对应数据
const tableCache = new Map();
// 记录每个表的加载状态:'idle'(未开始) | 'loading'(加载中) | 'loaded'(已完成)
const tableLoadStatus = new Map();
// 创建Web Worker实例
const dbWorker = new Worker('db-worker.js');

// 初始化需要批量加载的表列表
const initialTables = ['user', 'order', 'product', 'category'];
// 初始化每个表的状态为未开始
initialTables.forEach(table => tableLoadStatus.set(table, 'idle'));

// 向Worker发送初始加载任务
dbWorker.postMessage({
  type: 'init-tasks',
  tables: initialTables
});

// 监听Worker发来的消息,更新本地状态和缓存
dbWorker.onmessage = (e) => {
  const { type, table, data } = e.data;
  switch (type) {
    case 'table-loaded':
      tableCache.set(table, data);
      tableLoadStatus.set(table, 'loaded');
      console.log(`✅ 表${table}已加载至缓存`);
      // 这里可以添加UI更新逻辑,比如隐藏加载提示
      break;
    case 'task-started':
      tableLoadStatus.set(table, 'loading');
      console.log(`🔄 开始加载表${table}`);
      break;
    case 'task-canceled':
      tableLoadStatus.set(table, 'idle');
      console.log(`⏹️ 表${table}加载任务已取消`);
      break;
  }
};

// 用户点击查看表的处理函数
function handleViewTable(tableName) {
  const status = tableLoadStatus.get(tableName);
  
  if (status === 'loaded') {
    // 直接从缓存取数据渲染
    renderTable(tableCache.get(tableName));
    return;
  }
  
  if (status === 'loading') {
    console.log(`表${tableName}正在加载中,请稍候`);
    return;
  }
  
  // 未开始加载,发送抢占请求给Worker
  dbWorker.postMessage({
    type: 'prioritize-task',
    table: tableName
  });
  // 显示加载提示
  showLoadingIndicator(tableName);
}

// 辅助函数:渲染表数据到UI
function renderTable(data) {
  // 替换成你的UI渲染逻辑
  console.log('渲染数据:', data);
}

// 辅助函数:显示加载提示
function showLoadingIndicator(table) {
  console.log(`正在优先加载表${table}...`);
}

二、Web Worker逻辑:任务调度与IndexedDB操作

Worker负责维护任务队列、处理IndexedDB请求,并且支持中断正在执行的任务。这里关键是用AbortController来中断异步的IndexedDB请求,同时实现优先级队列:

// db-worker.js
let taskQueue = [];
// 当前正在执行的任务的控制器,用于中断请求
let currentTaskController = null;
// 当前正在处理的表名
let currentProcessingTable = null;
// IndexedDB实例
let dbInstance = null;

// 初始化IndexedDB连接
async function initIndexedDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('MyAppDatabase', 1);
    
    // 如果数据库版本升级,创建对应的objectStore(根据你的实际表结构调整)
    request.onupgradeneeded = (e) => {
      dbInstance = e.target.result;
      // 假设初始化时需要创建的表和主线程初始列表一致
      initialTables.forEach(table => {
        if (!dbInstance.objectStoreNames.contains(table)) {
          dbInstance.createObjectStore(table);
        }
      });
    };
    
    request.onsuccess = (e) => {
      dbInstance = e.target.result;
      resolve();
    };
    
    request.onerror = (e) => {
      reject(new Error(`IndexedDB初始化失败: ${e.target.error}`));
    };
  });
}

// 加载单个表的数据,支持通过AbortController中断
async function loadSingleTable(tableName, abortController) {
  return new Promise((resolve, reject) => {
    // 如果控制器已经中断,直接返回错误
    if (abortController.signal.aborted) {
      reject(new Error('任务已被中断'));
      return;
    }
    
    // 开启只读事务
    const transaction = dbInstance.transaction(tableName, 'readonly');
    const objectStore = transaction.objectStore(tableName);
    // 获取表中所有数据
    const getAllRequest = objectStore.getAll();
    
    // 监听中断信号,触发时中止IndexedDB请求
    abortController.signal.addEventListener('abort', () => {
      getAllRequest.abort();
    });
    
    getAllRequest.onsuccess = () => {
      resolve(getAllRequest.result);
    };
    
    getAllRequest.onerror = (e) => {
      reject(new Error(`加载表${tableName}失败: ${e.target.error}`));
    };
  });
}

// 处理任务队列的循环函数
async function processTasks() {
  while (taskQueue.length > 0) {
    // 取出队列最前面的任务(高优先级任务会被插入到队列头部)
    const currentTask = taskQueue.shift();
    currentProcessingTable = currentTask.table;
    currentTaskController = new AbortController();
    
    // 通知主线程任务开始
    self.postMessage({ type: 'task-started', table: currentTask.table });
    
    try {
      // 加载表数据
      const tableData = await loadSingleTable(currentTask.table, currentTaskController);
      // 通知主线程加载完成
      self.postMessage({ type: 'table-loaded', table: currentTask.table, data: tableData });
    } catch (error) {
      if (error.name === 'AbortError') {
        // 任务被中断,通知主线程
        self.postMessage({ type: 'task-canceled', table: currentTask.table });
      } else {
        console.error(`加载表${currentTask.table}出错:`, error);
      }
    } finally {
      // 重置当前任务状态
      currentTaskController = null;
      currentProcessingTable = null;
    }
  }
}

// 监听主线程发来的消息
self.onmessage = async (e) => {
  const { type, tables, table } = e.data;
  
  switch (type) {
    case 'init-tasks':
      // 初始化IndexedDB
      await initIndexedDB();
      // 将初始任务添加到队列(低优先级,放在队列尾部)
      taskQueue = tables.map(t => ({ table: t, priority: 'low' }));
      // 开始处理任务队列
      processTasks();
      break;
      
    case 'prioritize-task':
      // 先检查任务是否已经在队列中,避免重复添加
      const existingTaskIndex = taskQueue.findIndex(task => task.table === table);
      if (existingTaskIndex !== -1) {
        // 移除队列中已有的该任务
        taskQueue.splice(existingTaskIndex, 1);
      }
      
      // 如果当前正在处理的就是目标表,无需操作
      if (currentProcessingTable === table) {
        return;
      }
      
      // 如果当前有正在执行的任务,中断它
      if (currentTaskController) {
        currentTaskController.abort();
      }
      
      // 将高优先级任务插入到队列头部
      taskQueue.unshift({ table, priority: 'high' });
      
      // 如果当前没有在处理任务,启动任务处理循环
      if (!currentTaskController) {
        processTasks();
      }
      break;
  }
};

三、关键技术点说明

  1. AbortController中断异步请求:
    浏览器原生的AbortController可以用来中断支持signal参数的异步操作(包括IndexedDB请求、fetch等)。当需要抢占任务时,调用currentTaskController.abort()就能中断正在进行的IndexedDB请求,让Worker立刻处理高优先级任务。

  2. 优先级任务队列:
    当收到主线程的抢占请求时,我们会把目标任务从队列中移除(如果存在),然后插入到队列头部,确保下一个被处理的就是这个高优先级任务。

  3. 状态同步机制:
    主线程和Worker通过消息传递同步每个表的加载状态,避免重复请求或者状态不一致的问题。比如Worker开始加载、加载完成、任务取消时都会通知主线程更新本地状态。

  4. 任务去重:
    在添加高优先级任务前,先检查队列中是否已经存在该任务,如果有就移除后再添加到头部,避免队列中出现重复任务。

内容的提问来源于stack exchange,提问作者prmph

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:26:01