многопоточность — как гарантировать, что API вернется до того, как его обратный вызов будет вызван в асинхронном API (c ++)

Я разрабатываю библиотеку, которая имеет асинхронные API.
Один из API, когда он вызывается, создает задачу, помещает ее в поток задач и возвращает идентификатор задачи.
После того, как задача завершена, поток задач уведомляет вызывающего о результате, вызывая функцию обратного вызова.

Последовательность следующая

гость

C.1. API вызовов вызывающего абонента

С.2. Lib. создать задачу и поместить ее в очередь потока задач

С.3. Lib. пробуждает поток задач, вызывая notify_all для condition_variable

С.4. В этот момент может произойти переключение контекста, и этот поток будет приостановлен

С.6. После возобновления этой темы, Lib. возвращает идентификатор задачи

Поток задач

Т.1. Поток задач выполняет задачу.

Т.2. Когда задача завершена, поток задач уведомляет вызывающего абонента о результате, вызывая обратный вызов.

проблема

Вызывающая сторона проверяет данные результата функции обратного вызова по идентификатору задачи, но иногда обратный вызов вызывается до возврата API, и вызывающая сторона не может проверить результат.

Вопрос

Я хочу точно гарантировать, что API возвращает идентификатор задачи до ее обратного вызова.
Что я могу сделать?

Я использую lock_guard в теле API, чтобы не вызывать обратный вызов, и это значительно уменьшает возможность воспроизвести эту проблему.

Но поскольку 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);
}
}

0

Решение

По сути, вы не можете решить это на вашей стороне. Давайте предположим, что начальный поток приостанавливается точно после последней инструкции вашего кода, но до того, как вызывающая сторона получит идентификатор задачи. Вы просто не можете определить, когда вызывающая сторона сохранила идентификатор задачи таким образом, чтобы разрешить обратный вызов.

Вместо этого клиент должен кэшировать неизвестные идентификаторы задачи, когда он получил ожидающий вызов вашей асинхронной функции.

2

Другие решения