我开发了一个具有异步API的库。 其中一个API在调用时会创建一个任务,将其推送到任务线程并返回其任务ID。 任务完成后,任务线程通过调用回调函数将结果通知给调用者。
序列如下
呼叫者
C.1。呼叫者呼叫API
C.2。库。创建任务并将其推送到任务线程的队列
C.3。库。通过调用condition_variable
的notify_all唤醒任务线程C.4。此时,可以发生上下文切换,并且该线程将被暂停
C.6。在此线程恢复后,Lib。返回任务ID
任务主题
T.1。任务线程执行任务。
T.2。任务完成后,任务线程通过调用回调
将结果通知给调用者问题
调用者按任务ID检查回调函数的结果数据,但偶尔会在API返回之前调用回调,并且调用者无法检查结果。
问题
我想完全保证API在调用其回调之前返回任务ID。 我该怎么办?
我在API的主体中使用lock_guard来防止调用回调,并且减少了显着重现此问题的可能性。
但是因为lock_guard在API返回之前解锁了mutext,如果在互斥锁解锁之后发生了context-switch,那么在API返回之前,这个问题可以很少再现。
我也想防止这种情况。
汇总代码
long AcbCore::setState(long appState, long playState) // API
{
StateTask* task = (StateTask*) createTask(TaskType::PLAYER_STATE_CHANGE, &isAppSwitchingStateFlag, appState);
std::lock_guard<std::mutex> lockGd (*task->getEventMutex());
pushTask(task); // at this position, context switch can occur by condTaskThread_.notify_all() of resumeLoop()
return taskId;
}
void AcbCore::pushTask(Task* task)
{
mtxTaskQueue_.lock();
queueTask_.push_back(task);
mtxTaskQueue_.unlock();
resumeLoop();
}
void AcbCore::resumeLoop()
{
mtxTaskThread_.lock();
mtxTaskThread_.unlock();
condTaskThread_.notify_all();
}
bool AcbCore::suspendLoop()
{
bool isTimeout = false;
if (ingTask_ != NULL) {
isTimeout = (condTaskThread_.wait_for(lockTaskThread_, std::chrono::seconds(AWAKE_TASK_THREAD_TIMEOUT)) == std::cv_status::timeout);
} else {
condTaskThread_.wait(lockTaskThread_);
}
return isTimeout;
}
void AcbCore::taskLoop() // loop of Task Thread
{
Task* task = NULL;
Action* action = NULL;
while (isInitialized_) {
while (popTask(task)) {
if (task->isCompleted()) {
fireEvent(task);
} else {
doNextTask(task);
}
}
if (suspendLoop()) { // if awaked by timeout
cancelTask(ingTask_, true);
}
}
}
void AcbCore::fireEvent(Task* task, bool bDelete)
{
std::string errorInfo = task->getErrorInfo();
task->waitToUnlockEvent();
// eventHandler_ : callback set by caller when Acb is initialized
eventHandler_(task->getTaskId(), task->getEventType(), appState_.now_, playState_.now_, errorInfo.c_str());
if (bDelete) {
deleteTask(task);
}
}
答案 0 :(得分:2)
从根本上说,你无法解决这个问题。假设初始线程在代码的最后一条指令之后,但在调用者获取任务ID之前完全挂起。您无法确定调用者何时以允许回调发生的方式存储任务ID。
相反,客户端应该在对异步函数进行未完成的调用时缓存未知的任务ID。