Если я правильно вас понимаю, вы пытаетесь настроить следующую последовательность событий:
Client AMQPClient
| lockResource(id) |
|------------------------------>|
| |
| |--\
| | |
| resourceLocked | | try to acquire resource
|<------------------------------| |
/--| |<-/
do_stuff | | |
\->| |
| |
, где сообщение resourceLocked
не отправляется, когда AMQP
не может получить ресурс и, таким образом, do_stuff
также не вызывается в этом случае.
Проблема с do_stuff
, которая не вызывается при использовании реализации 1, связана с состоянием гонки, когда несколько клиентов ожидают одновременной блокировки соответствующего ресурса. Одна последовательность, демонстрирующая описанную вами проблему (плюс дополнительные проблемы с этим подходом, а также с вашей реализацией 3), выглядит следующим образом:
- Клиент A входит в функцию 1: он отключает все существующие подключения к
AmqpClient::resourceLocked
, а затем подключается к этому сигналу.
- Клиент B теперь также входит в функцию 1: он также отключает все существующие соединения с
AmpqClient::resourceLocked
, в частности, соединение, только что установленное клиентом A. Затем он подключается к этому сигналу.
-
AmpqClient
обрабатывает запрос клиента lockResource
. Он получил запрошенный ресурс и издает сигнал resourceLocked
.
- Клиент B, единственный подключенный к сигналу, теперь выполняет
do_stuff
, хотя это был запрошенный клиентом ресурс.
-
AmpqClient
обрабатывает запрос клиента B lockResource
. Опять же, он успешно получает ресурс и испускает сигнал resourceLocked
.
- Клиент B, все еще подключенный к этому сигналу, снова выполняет
do_stuff
.
Вышеприведенная последовательность показывает, что do_stuff
клиента A, который не вызывается, не так уж и плох, учитывая, что клиент B будет работать, пока его ресурс еще не получен.
Чтобы исправить это, вы должны убедиться, что вы только do_stuff
звоните клиенту, чей запрос lockResource
был только что обработан. Сигнал, который по замыслу всегда уведомляет всех наблюдателей, является просто неоптимальным подходом, поскольку тогда наблюдатели должны будут проверить, должен ли сигнал уведомлять их или кого-либо еще.
Первым исправлением является изменение сигнала lockResource
, чтобы он также отправлял указатель this
клиента, который излучает сигнал. Таким образом, AmqpClient
может использовать этот указатель для обратного вызова клиента, когда он получил ресурс.
Одна реализация слота, подключенного к сигналу, может выглядеть так:
void AmqpClient::handleLockResourceRequest(int identifier,
QObject* requestingClient)
{
// try to acquire resource described by `identifier`
if (resource_acquired_successfully)
{
QMetaObject::invokeMethod(requestingClient, "do_stuff", Qt::AutoConnection);
}
}
Обратите внимание, что для работы invokeMethod
, do_stuff
должен быть либо слотом, либо должен быть помечен как Q_INVOKABLE
.
Но я бы пошел на шаг дальше, чем просто вызвать do_stuff
через invokeMethod
: Насколько я могу сказать, AmqpClient
- единственный слушатель, подключенный к lockResource
, и эти два объекта были в тот же поток, который вы, вероятно, просто использовали бы прямой вызов amqp_->tryToLockResource(identifier)
вместо
emit lockResource(identifier)
. То есть единственная причина, по которой вы используете сигнал, заключается в том, что вызов проходит через цикл обработки событий.
Вместо использования сигнала вы также можете использовать invokeMethod
для запроса получения ресурса через цикл обработки событий. Преимущество этого в том, что ваш клиентский класс больше не раскрывает тот факт, что ему необходимо приобретать ресурсы, чтобы выполнять свою работу пользователям этого класса.
В итоге полученный код будет выглядеть примерно так:
class ClientType : public QObject {
Q_OBJECT
public:
// ...
Q_INVOKABLE void do_stuff(); // definition as before
private:
void requestResource(); // was previously code block 1 in your question
private:
AmqpClient* amqp_;
// ...
};
inline void ClientType::requestResource()
{
auto identifier = ...; // create resource identifier
QMetaObject::invokeMethod(amqp_,
"requestResource",
Qt::AutoConnection,
Q_ARG(int, identifier),
Q_ARG(QObject*, this));
}
class AmqpClient : public QObject {
Q_OBJECT
public:
// ...
Q_INVOKABLE requestResource(int identifier, QObject* requestingClient);
};
inline void AmqpClient::requestResource(int identifier,
QObject* requestingClient)
{
// try to acquire resource described by `identifier`
if (resource_acquired_successfully)
{
QMetaObject::invokeMethod(requestingClient, "do_stuff", Qt::AutoConnection);
}
}
В приведенной выше реализации я предположил, что идентификаторы ресурсов имеют тип int
, но, конечно, вы можете использовать любой другой тип, зарегистрированный в системе метатипов Qt. Аналогично, вместо передачи QObject
-пунктов вы также можете использовать ClientType*
(или абстрактный базовый класс, чтобы избежать введения циклических зависимостей) после регистрации типа указателя в системе метатипов Qts.