【问题标题】:How to guarantee that the API returns before its callback is invoked in asyncronous API (c++)如何保证API在异步API中调用它的回调之前返回(c ++)
【发布时间】:2014-07-15 05:08:29
【问题描述】:

我开发了一个具有异步 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返回之前解锁了mutex,如果在mutex解锁之后发生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);
    }
}

【问题讨论】:

  • 不要将完成作为回调,而是将其设置为信号对象。然后调用者可以检查信令对象。 (无论哪种方式,你都必须有某种形式的同步对象,因为回调将在与调用者不同的线程中)
  • 感谢您的热心回复,但我无法编辑来电者的代码,所以应该在图书馆管理。
  • 我使用条件变量来控制两个线程,但是当任务线程被notify_all恢复时,调用者的线程可以被上下文切换挂起并在API返回之前调用回调
  • 您根本无法编辑来电者?因为如果您再进行一次 API 调用以先同步生成任务 ID,然后让调用者将其与请求一起传递,则很容易解决。
  • 很难从口头描述中理解发生了什么。请发布(伪)代码,详细说明对调用者和任务线程之间共享数据的每次访问,以及每次相关的同步调用。

标签: c++ multithreading context-switch


【解决方案1】:

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

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

【讨论】:

  • 谢谢。这对我来说太难过了。但是你的回答让我摆脱了这个问题的困扰。
猜你喜欢
  • 1970-01-01
  • 2014-11-26
  • 2013-02-02
  • 2015-09-03
  • 2018-11-24
  • 1970-01-01
  • 2022-10-17
  • 1970-01-01
  • 2015-10-22
相关资源
最近更新 更多