HarmonyOS鸿蒙Next中使用TaskPool进行关系型数据库操作遇到的问题
HarmonyOS鸿蒙Next中使用TaskPool进行关系型数据库操作遇到的问题 1.不能通过@Concurrent修饰的函数将Context传入,显示Parameter error.Illegal context.
2.在主线程初始化数据库之后,将获取数据库数据接口进入taskpool,不能够访问数据,显示RDB store not initialized。
可是根据文档 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/batch-database-operations-guide#使用sendable进行大容量数据库操作 ,确实就可以通过taskpool操作数据库呢。
求大神指点。。。。。。挺着急的–!
更多关于HarmonyOS鸿蒙Next中使用TaskPool进行关系型数据库操作遇到的问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html
开发者您好,根据官网提供的demo:使用TaskPool进行频繁数据库操作,并没有复现您如上的两个问题,麻烦您提供如下信息吧:
1、复现代码(如最小复现demo);
2、版本信息(如:开发工具、手机系统版本信息);
更多关于HarmonyOS鸿蒙Next中使用TaskPool进行关系型数据库操作遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
大神您好:我现在使用的开发工具为:devecostudio/5.0.0(12),手机系统版本为Mate 60 pro 6.0.0.130
demo如下:
//ConcurrentFunctions
// ConcurrentFunctions
import { DownloadDatabaseManager, DownloadState } from '../manager/DownloadDatabaseManager';
import { DownloadInfo } from '../model/DownloadInfo';
import { common } from '[@kit](/user/kit).AbilityKit';
/**
* 初始化数据库
*/
@Concurrent
export async function initDatabaseConcurrent(context: common.UIAbilityContext): Promise<boolean> {
try {
const dbManager = DownloadDatabaseManager.getInstance();
const result = await dbManager.initDatabase(context);
return result; // 直接返回 boolean
} catch (error) {
console.error('initDatabaseConcurrent error:', error);
return false;
}
}
/**
* 获取所有下载信息
*/
@Concurrent
export async function getAllDownloadInfoConcurrent(): Promise<DownloadState> {
try {
const dbManager = DownloadDatabaseManager.getInstance();
const result = await dbManager.getDownloadData();
return result;
} catch (error) {
console.error('getAllDownloadInfoConcurrent error:', error);
return {
downloading: [],
downloaded: []
};
}
}
/**
* 获取所有下载信息
*/
@Concurrent
export async function batchSaveDownloadInfoConcurrent(validInfos: DownloadInfo[]): Promise<string[]> {
try {
const dbManager = DownloadDatabaseManager.getInstance();
const result = await dbManager.batchSaveDownloadInfo(validInfos);
return result
} catch (error) {
console.error('batchSaveDownloadInfoConcurrent error:', error);
return []
}
}
//DownloadDatabaseManager
import { relationalStore } from '[@kit](/user/kit).ArkData';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';
import { bundleManager } from '[@kit](/user/kit).AbilityKit';
import { common } from '[@kit](/user/kit).AbilityKit';
import { DatabaseConstants } from '../util/DatabaseConstants';
import { DownloadInfo } from '../model/DownloadInfo';
import { logger } from '../util/DownloadLogger';
export class DownloadState {
downloading: DownloadInfo[] = [];
downloaded: DownloadInfo[] = [];
}
const TAG: string = 'DownloadDatabaseManager';
/**
* 数据库版本常量 - 统一使用 DatabaseConstants 中的定义
*/
const DATABASE_VERSION = DatabaseConstants.DATABASE_VERSION;
const DATABASE_NAME = DatabaseConstants.DATABASE_NAME;
export class DownloadDatabaseManager {
private static sInstance: DownloadDatabaseManager | null = null;
private rdbStore: relationalStore.RdbStore | null = null;
private isInitialized: boolean = false;
private currentVersion: number = 0; // 当前数据库版本
public static getInstance(): DownloadDatabaseManager {
if (DownloadDatabaseManager.sInstance == null) {
DownloadDatabaseManager.sInstance = new DownloadDatabaseManager();
}
return DownloadDatabaseManager.sInstance;
}
/**
* 初始化数据库
*/
public async initDatabase(context: common.UIAbilityContext): Promise<boolean> {
if (this.isInitialized) {
return true;
}
try {
const STORE_CONFIG: relationalStore.StoreConfig = {
name: DATABASE_NAME,
securityLevel: relationalStore.SecurityLevel.S1
};
this.rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
// 获取当前数据库版本
const oldVersion = await this.getDatabaseVersion();
this.currentVersion = oldVersion;
logger.info(TAG, `Database version: old=${oldVersion}, new=${DATABASE_VERSION}`);
// 检查版本并执行相应操作
if (oldVersion === 0) {
// 首次创建数据库
logger.info(TAG, 'Creating database tables for the first time');
await this.createTable();
await this.setDatabaseVersion(DATABASE_VERSION);
} else if (oldVersion < DATABASE_VERSION) {
// 需要升级
logger.info(TAG, `Database needs upgrade from version ${oldVersion} to ${DATABASE_VERSION}`);
await this.onUpgrade(oldVersion, DATABASE_VERSION);
} else if (oldVersion > DATABASE_VERSION) {
// 版本降级 - 数据库版本高于应用版本
logger.warn(TAG, `Database version ${oldVersion} is higher than app version ${DATABASE_VERSION}`);
throw new Error(`Database version ${oldVersion} is not compatible with app version ${DATABASE_VERSION}. Please upgrade the app.`);
} else {
// 版本一致,无需操作
logger.info(TAG, 'Database version is up to date');
}
this.currentVersion = DATABASE_VERSION;
this.isInitialized = true;
logger.info(TAG, 'Database initialized successfully');
return true;
} catch (error) {
const err: BusinessError = error as BusinessError;
logger.error(TAG, `Failed to initialize database: ${err.code} - ${err.message}`);
return false;
}
}
/**
* 同步设置数据库版本
*/
private setDatabaseVersionSync(version: number): void {
if (!this.rdbStore) {
return;
}
try {
// 创建版本表(支持历史记录)
const createTableSql = `
CREATE TABLE IF NOT EXISTS db_version (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version INTEGER NOT NULL,
upgrade_time INTEGER NOT NULL,
app_version TEXT
)
`;
this.rdbStore.executeSync(createTableSql);
// 插入新版本记录(保留历史)
const upgradeTime = Date.now();
const appVersion = this.getAppVersion();
const insertSql = `
INSERT INTO db_version (version, upgrade_time, app_version)
VALUES (${version}, ${upgradeTime}, '${appVersion}')
`;
this.rdbStore.executeSync(insertSql);
logger.info(TAG, `Database version set to ${version}, app version: ${appVersion} (sync)`);
} catch (error) {
const err: BusinessError = error as BusinessError;
logger.error(TAG, `Failed to set database version (sync): ${err.message}`);
if (error instanceof Error) {
throw error;
} else {
throw new Error(String(error));
}
}
}
/**
* 同步创建完整的下载信息表
*/
private createTableSync(): void {
if (!this.rdbStore) {
throw new Error('RDB store not initialized');
}
const CREATE_TABLE_SQL: string = `
CREATE TABLE IF NOT EXISTS ${DatabaseConstants.TABLE_DOWNLOAD_INFO} (
${DatabaseConstants.COLUMN_ID} INTEGER PRIMARY KEY AUTOINCREMENT,
${DatabaseConstants.COLUMN_VIDEOID} TEXT NOT NULL UNIQUE,
${DatabaseConstants.COLUMN_TITLE} TEXT,
${DatabaseConstants.COLUMN_FORMAT} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_LANGUAGE} TEXT,
${DatabaseConstants.COLUMN_SHOWID} TEXT,
${DatabaseConstants.COLUMN_SHOWNAME} TEXT,
${DatabaseConstants.COLUMN_SHOW_VIDEOSEQ} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SHOWEPISODE_TOTAL} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_CATS} TEXT,
${DatabaseConstants.COLUMN_SECONDS} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SEGS_SECONDS} TEXT,
${DatabaseConstants.COLUMN_STATE} INTEGER DEFAULT -1,
${DatabaseConstants.COLUMN_IS_PANORAMA} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_VIDEO_TYPE} TEXT,
${DatabaseConstants.COLUMN_DRM_TYPE} TEXT,
${DatabaseConstants.COLUMN_STREAM_TYPE} TEXT,
${DatabaseConstants.COLUMN_STREAM_TOKEN} TEXT,
${DatabaseConstants.COLUMN_FILE_FORMAT} TEXT,
${DatabaseConstants.COLUMN_M3U8_URL} TEXT,
${DatabaseConstants.COLUMN_M3U8_FILE_ID} TEXT,
${DatabaseConstants.COLUMN_LICENSE_NUM} TEXT,
${DatabaseConstants.COLUMN_YOUKU_REGISTER_NUM} TEXT,
${DatabaseConstants.COLUMN_PAY_TYPE} TEXT,
${DatabaseConstants.COLUMN_EXTRA_INFO} TEXT,
${DatabaseConstants.COLUMN_DOWNLOAD_TYPE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SAVE_ROOT_PATH} TEXT,
${DatabaseConstants.COLUMN_PCDN_REPAIRED} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_TASK_ID} TEXT,
${DatabaseConstants.COLUMN_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_DOWNLOADED_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_CREATE_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_CAN_PLAY} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_DOWNLOADED_SECONDS} REAL DEFAULT 0,
${DatabaseConstants.COLUMN_SEG_COUNT} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SEG_ID} INTEGER DEFAULT 1,
${DatabaseConstants.COLUMN_SEG_DOWNLOADED_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SEGS_SIZE} TEXT,
${DatabaseConstants.COLUMN_TOKEN} TEXT,
${DatabaseConstants.COLUMN_OIP} TEXT,
${DatabaseConstants.COLUMN_SID} TEXT,
${DatabaseConstants.COLUMN_THREAD_NUM} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_PLAY_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_LAST_PLAY_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IMG_URL} TEXT,
${DatabaseConstants.COLUMN_START_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_GET_URL_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_FINISH_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_LAST_UPDATE_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_EXCEPTION_ID} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_PROGRESS} REAL DEFAULT 0,
${DatabaseConstants.COLUMN_POINTS} TEXT,
${DatabaseConstants.COLUMN_PREVIEW} TEXT,
${DatabaseConstants.COLUMN_IS_SHOW_WATERMARK} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_EXCLUSIVE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_SUBTITLES_DOWNLOAD_FINISHED} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_PREVIEW_DOWNLOAD_FINISHED} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_VIDEO_THUMB_DOWNLOAD_FINISHED} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_ENCRYPTION} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_VERSION_CODE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_VERSION_NAME} TEXT,
${DatabaseConstants.COLUMN_IS_VERTICAL_VIDEO} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SHOW_SUBTITLE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_EXPOSURE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_PUSH_DOWNLOAD} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_VIDEO_DOWNLOAD_PASSWORD} TEXT,
${DatabaseConstants.COLUMN_SAVE_PATH} TEXT NOT NULL,
${DatabaseConstants.COLUMN_RETRY} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_IS_THUMBNAIL_DOWNLOADING} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_HEADER_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SPEED} TEXT,
${DatabaseConstants.COLUMN_SUBTITLES_JSON} TEXT,
${DatabaseConstants.COLUMN_AD} TEXT,
${DatabaseConstants.COLUMN_STREAM} TEXT,
${DatabaseConstants.COLUMN_INTELLIGENT_FOLDER} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_INTELLIGENT_RESTART_FROM_USER} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_EXTRA_INFO_MEM} TEXT,
${DatabaseConstants.COLUMN_SEG_INFOS} TEXT,
${DatabaseConstants.COLUMN_IS_DELETED} INTEGER DEFAULT 0
)
`;
this.rdbStore.executeSync(CREATE_TABLE_SQL);
// 创建索引
const INDEXES: string[] = [
`CREATE INDEX IF NOT EXISTS idx_videoid ON ${DatabaseConstants.TABLE_DOWNLOAD_INFO}(${DatabaseConstants.COLUMN_VIDEOID})`,
`CREATE INDEX IF NOT EXISTS idx_state ON ${DatabaseConstants.TABLE_DOWNLOAD_INFO}(${DatabaseConstants.COLUMN_STATE})`,
`CREATE INDEX IF NOT EXISTS idx_showid ON ${DatabaseConstants.TABLE_DOWNLOAD_INFO}(${DatabaseConstants.COLUMN_SHOWID})`,
`CREATE INDEX IF NOT EXISTS idx_show_videoseq ON ${DatabaseConstants.TABLE_DOWNLOAD_INFO}(${DatabaseConstants.COLUMN_SHOW_VIDEOSEQ})`,
`CREATE INDEX IF NOT EXISTS idx_create_time ON ${DatabaseConstants.TABLE_DOWNLOAD_INFO}(${DatabaseConstants.COLUMN_CREATE_TIME})`
];
for (const indexSql of INDEXES) {
try {
this.rdbStore.executeSync(indexSql);
} catch (error) {
logger.warn(TAG, `Failed to create index (sync): ${error}`);
}
}
logger.info(TAG, 'Database table and indexes created successfully (sync)');
}
/**
* 同步数据库升级处理
*
* [@param](/user/param) oldVersion 旧版本号
* [@param](/user/param) newVersion 新版本号
*/
private onUpgradeSync(oldVersion: number, newVersion: number): void {
if (!this.rdbStore) {
throw new Error('RDB store not initialized');
}
logger.info(TAG, `Starting database upgrade from version ${oldVersion} to ${newVersion} (sync)`);
try {
// 开启事务
this.rdbStore.beginTransaction();
// 增量升级:从旧版本逐步升级到新版本
let currentVersion = oldVersion;
// 版本 0 -> 1: 创建初始表结构
if (currentVersion < 1) {
logger.info(TAG, 'Upgrading to version 1: Creating initial tables (sync)');
this.createTableSync();
currentVersion = 1;
}
// 更新版本号
this.setDatabaseVersionSync(newVersion);
// 提交事务
this.rdbStore.commit();
logger.info(TAG, `Database upgrade completed successfully to version ${newVersion} (sync)`);
} catch (error) {
// 升级失败,回滚事务
this.rdbStore.rollBack();
const err: BusinessError = error as BusinessError;
logger.error(TAG, `Database upgrade failed (sync): ${err.message}`);
throw new Error(`Database upgrade failed from version ${oldVersion} to ${newVersion}: ${err.message}`);
}
}
/**
* 创建完整的下载信息表
*/
private async createTable(): Promise<void> {
if (!this.rdbStore) {
throw new Error('RDB store not initialized');
}
const CREATE_TABLE_SQL: string = `
CREATE TABLE IF NOT EXISTS ${DatabaseConstants.TABLE_DOWNLOAD_INFO} (
${DatabaseConstants.COLUMN_ID} INTEGER PRIMARY KEY AUTOINCREMENT,
${DatabaseConstants.COLUMN_VIDEOID} TEXT NOT NULL UNIQUE,
${DatabaseConstants.COLUMN_TITLE} TEXT,
${DatabaseConstants.COLUMN_FORMAT} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_LANGUAGE} TEXT,
${DatabaseConstants.COLUMN_SHOWID} TEXT,
${DatabaseConstants.COLUMN_SHOWNAME} TEXT,
${DatabaseConstants.COLUMN_SHOW_VIDEOSEQ} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SHOWEPISODE_TOTAL} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_CATS} TEXT,
${DatabaseConstants.COLUMN_SECONDS} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SEGS_SECONDS} TEXT,
${DatabaseConstants.COLUMN_STATE} INTEGER DEFAULT -1,
${DatabaseConstants.COLUMN_IS_PANORAMA} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_VIDEO_TYPE} TEXT,
${DatabaseConstants.COLUMN_DRM_TYPE} TEXT,
${DatabaseConstants.COLUMN_STREAM_TYPE} TEXT,
${DatabaseConstants.COLUMN_STREAM_TOKEN} TEXT,
${DatabaseConstants.COLUMN_FILE_FORMAT} TEXT,
${DatabaseConstants.COLUMN_M3U8_URL} TEXT,
${DatabaseConstants.COLUMN_M3U8_FILE_ID} TEXT,
${DatabaseConstants.COLUMN_LICENSE_NUM} TEXT,
${DatabaseConstants.COLUMN_YOUKU_REGISTER_NUM} TEXT,
${DatabaseConstants.COLUMN_PAY_TYPE} TEXT,
${DatabaseConstants.COLUMN_EXTRA_INFO} TEXT,
${DatabaseConstants.COLUMN_DOWNLOAD_TYPE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SAVE_ROOT_PATH} TEXT,
${DatabaseConstants.COLUMN_PCDN_REPAIRED} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_TASK_ID} TEXT,
${DatabaseConstants.COLUMN_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_DOWNLOADED_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_CREATE_TIME} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_CAN_PLAY} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_DOWNLOADED_SECONDS} REAL DEFAULT 0,
${DatabaseConstants.COLUMN_SEG_COUNT} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SEG_ID} INTEGER DEFAULT 1,
${DatabaseConstants.COLUMN_SEG_DOWNLOADED_SIZE} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_SEGS_SIZE} TEXT,
${DatabaseConstants.COLUMN_TOKEN} TEXT,
${DatabaseConstants.COLUMN_OIP} TEXT,
${DatabaseConstants.COLUMN_SID} TEXT,
${DatabaseConstants.COLUMN_THREAD_NUM} INTEGER DEFAULT 0,
${DatabaseConstants.COLUMN_PLAY_TIME} INTEGER DEFAULT 0,
${大神,还有个问题要请教一下您,taskpool子线程可以直接操作HashMap吗,我们这边也出现过这样的提示,说不可以操作HashMap这种复杂数据类型
在HarmonyOS Next中,TaskPool不支持直接操作关系型数据库。关系型数据库操作需在主线程或使用特定数据库API的异步方法进行。TaskPool适用于CPU密集型任务,如数据处理,不涉及UI或数据库访问。若需异步执行数据库操作,应使用数据库提供的异步接口或通过Worker线程处理。
根据你的描述,这是HarmonyOS Next中TaskPool与关系型数据库(RDB)交互的两个典型限制。我来为你解释原因和解决方案:
1. 关于@Concurrent函数无法传入Context:
这是预期行为,并非错误。@Concurrent修饰的函数(TaskPool任务)运行在独立的并发线程上,其参数必须满足“Sendable”规则(即可以安全跨线程传递)。应用的Context对象包含与当前UI线程/主线程强关联的资源,不允许直接跨线程传递,因此会报“Illegal context”参数错误。
2. 关于“RDB store not initialized”错误:
这个问题的核心在于RDB数据库实例(RdbStore)本身不是Sendable对象。虽然你在主线程初始化了数据库,但直接将RdbStore实例或依赖它的接口传入TaskPool是无法在子线程中访问的。
正确的实现方式(遵循你提供的指南):
文档中“使用Sendable进行大容量数据库操作”的关键是:不直接传递RdbStore,而是将数据库操作封装成“数据访问对象(DAO)模式”,并将需要操作的数据本身作为Sendable参数传递。
具体步骤修正:
- 在主线程:初始化获取
RdbStore实例。 - 封装操作:将你需要进行的数据库操作(如批量插入、查询等)所必需的数据(如对象数组、查询条件等)提取出来,确保这些数据是Sendable的(如普通对象、数组、基本类型)。
- 在
@Concurrent函数中:函数参数只接收这些Sendable数据。在函数内部,通过全局访问点(如获取ApplicationContext)重新获取或创建新的RDB连接,然后使用传入的数据执行操作。 - 使用Sendable:确保你的数据类用
@Sendable装饰器标记,使其可以安全传递。
简单示例思路:
// 1. 定义Sendable数据类
@Sendable
class OperationData {
items: Array<YourDataModel>;
}
// 2. @Concurrent函数只接收Sendable数据
@Concurrent
async function batchInsertInTaskPool(opData: OperationData): Promise<void> {
// 3. 在函数内部,通过全局方式获取Context并初始化数据库连接
const context = ...; // 通过合适的方式获取ApplicationContext
const helper = new YourRdbHelper(context); // 重新初始化helper
const rdbStore = await helper.getRdbStore(); // 获取当前线程的store实例
// 4. 使用opData.items进行批量插入操作
await doBatchInsert(rdbStore, opData.items);
}
// 5. 在主线程调用
let dataToProcess: OperationData = ...; // 准备数据
TaskPool.execute(batchInsertInTaskPool, dataToProcess);
总结:
你不能跨线程传递Context或RdbStore。正确做法是将数据通过Sendable方式传递到TaskPool,在TaskPool任务内部重新建立数据库连接来操作这些数据。文档中的方案正是基于此设计,请仔细检查你的实现是否将操作与数据分离。

