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

时间:2014-07-15 05:08:29

标签: c++ multithreading context-switch

我开发了一个具有异步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);
    }
}

1 个答案:

答案 0 :(得分:2)

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

相反,客户端应该在对异步函数进行未完成的调用时缓存未知的任务ID。