在异步API (c++)中,如何保证API在调用回调之前返回

How to guarantee that the API returns before its callback is invoked in asyncronous API (c++)

本文关键字:API 调用 回调 返回 何保证 异步 c++      更新时间:2023-10-16

我开发了一个具有异步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返回之前解锁互斥对象,如果上下文切换发生在互斥对象解锁之后,在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);
    }
}

从根本上说,你无法解决这个问题。让我们假设初始线程正好在代码的最后一条指令之后挂起,但在调用者获得任务ID之前。您根本无法确定调用者何时以允许发生回调的方式存储了任务ID。

相反,客户端应该缓存未知的任务id,当他有一个未完成的调用你的异步函数。