背景
在開發(fā)鴻蒙的cronet版本時(shí)候,發(fā)現(xiàn)如果同時(shí)用for循環(huán)發(fā)起多個(gè)網(wǎng)絡(luò)請求,在cronet網(wǎng)絡(luò)線程回調(diào)到uv_queue_work時(shí)候,after_work_cb只會(huì)回調(diào)一次,然后js主線程就ANR了,一臉懵逼的我,我一度懷疑自己理解錯(cuò)鴻蒙的線程模型。
問題
在第一個(gè)after_work_cb觸發(fā)時(shí)候,調(diào)用《C++處理業(yè)務(wù)代碼》里面的 “workData->handler_(workData->env_, workData->data_)”,會(huì)回調(diào)到RequestCallback函數(shù)里面,觸發(fā)了死循環(huán),把ArkTS線程(主線程)卡死了,在3秒系統(tǒng)檢測后就會(huì)ANR退出app。因?yàn)閖s調(diào)用C++由于是另外開辟線程(“std::thread testThread(StartThread, asyncContext);”)添加任務(wù)到uv_queue里面,所以日志打印是可以看到對應(yīng)循環(huán)次數(shù)的 "add uv_queue_work" 輸出,但是日志只會(huì)輸出一次“WorkData::workData->handler_ before”一次調(diào)用,如果后面不加日志來看,就造成了一種after_work_cb不調(diào)用的假象,其實(shí)是主線程被我們的函數(shù)給卡死了,沒機(jī)會(huì)執(zhí)行隊(duì)列中下一個(gè)函數(shù)。
JS代碼
- 在NativeMultiThreads/entry/src/main/cpp/types/libentry/index.d.ts定義好下面js接口
export const queueWork: (arg: number) => Promise<number>;
C++層代碼
- 下面是定義函數(shù)傳遞內(nèi)容,持有js的上下文,和js對應(yīng)映射的c++方法
struct CallbackContext {
napi_env env = nullptr;
napi_deferred deferred_;
int32_t retData = 0;
};
static napi_value QueueWork(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value argv[1] = {nullptr};
napi_value thisVar = nullptr;
void *data = nullptr;
napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
// 參數(shù)檢查
if (argc < 1) {
napi_throw_error(env, "ParameterError", "one param expected");
return nullptr;
}
// 保存env和回調(diào)函數(shù),以便后續(xù)回調(diào)
auto asyncContext = new CallbackContext();
napi_value result = nullptr;
napi_create_promise(env, &asyncContext->deferred_, &result);
asyncContext->env = env;
// napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
// 模擬拋到非js線程執(zhí)行邏輯
std::thread testThread(StartThread, asyncContext);
testThread.detach();
return result;
}
C++處理業(yè)務(wù)代碼
void RequestCallback(napi_env env, void *data) {
CallbackContext *context = static_cast<CallbackContext *>(data);
napi_value retArg;
napi_create_int32(env, context->retData, &retArg);
napi_resolve_deferred(env, context->deferred_, retArg);
//todo 死循環(huán)
while(1) {
}
}
//子線程調(diào)用函數(shù),主要問題出在RequestCallback函數(shù)里
void StartThread(CallbackContext *context) {
// 從env中獲取對應(yīng)的ArkTS線程的loop,此處的env需要在注冊JS回調(diào)時(shí)保存下來。
uv_loop_s *loop = nullptr;
napi_get_uv_event_loop(context->env, &loop);
context->retData = 1;
auto workData = new WorkData(context->env, context, RequestCallback);
auto work = new uv_work_t;
work->data = reinterpret_cast<void *>(workData);
OH_LOG_Print(LOG_APP, LogLevel::LOG_INFO, CRONET_LOG_DOMAIN, "magic native", "add uv_queue_work");
// 拋任務(wù)到ArkTS線程
uv_queue_work(
loop, work,
// work_cb
// 此回調(diào)在另一個(gè)普通線程中執(zhí)行,用于處理異步或者耗時(shí)任務(wù),回調(diào)執(zhí)行完后執(zhí)行下面的回調(diào)。本示例中無需執(zhí)行任務(wù)。
[](uv_work_t *work) {},
// after_work_cb 此回調(diào)在env對應(yīng)的ArkTS線程中執(zhí)行。
[](uv_work_t *work, int status) {
OH_LOG_Print(LOG_APP, LogLevel::LOG_INFO, CRONET_LOG_DOMAIN, "magic native", "uv_queue_work callback");
auto workData = static_cast<WorkData *>(work->data);
napi_env env = workData->env_;
napi_handle_scope scope = nullptr;
// 打開handle scope用于管理napi_value的生命周期,否則會(huì)內(nèi)存泄露。
napi_open_handle_scope(env, &scope);
if (scope == nullptr) {
return;
}
OH_LOG_Print(LOG_APP, LogLevel::LOG_INFO, CRONET_LOG_DOMAIN, "magic native", "WorkData::workData->handler_ before");
workData->handler_(workData->env_, workData->data_);
OH_LOG_Print(LOG_APP, LogLevel::LOG_INFO, CRONET_LOG_DOMAIN, "magic native",
"WorkData::workData->handler_ after");
napi_close_handle_scope(env, scope);
if (work != nullptr) {
delete work;
}
delete workData;
});
}
解決
- 把《 C++處理業(yè)務(wù)代碼》里面的 while死循環(huán)刪除掉,after_work_cb 就可以正常工作了
總結(jié)
- 官方uv_queue_work介紹
- 官網(wǎng)基本api都會(huì)給出簡單示例,自己多結(jié)合項(xiàng)目排查問題即可