Приложение Qt вылетает при удалении выполненной задачи

У меня многопоточный сервер (наследует QTcpServer). Когда появляется новое соединение, я создаю новую задачу (наследует QRunnable), передаю дескриптор сокета конструктору и отправляю эту задачу в QThreadpool (имеет 3 рабочих).

QThreadPool::globalInstance()->start(task);

В run () я динамически создаю QTcpSocket, устанавливаю дескриптор сокета и читаю первый полученный байт. На основе значения этого байта я создаю новую конкретную задачу (также наследует QRunnable), передавая ее указатель ctr на ранее созданный объект QTcpSocket, а также отправляю эту задачу в QThreadpool.

Эта конкретная задача вызывает некоторые рутинные сбои приложений. Из файла журнала я вижу, что был вызван деструктор этой конкретной задачи.

Также Qt Creator выдает следующее сообщение об ошибке:

QObject: невозможно создать потомков для родителя, который находится в другом потоке. (Родительский поток - QNativeSocketEngine (0x18c62290), родительский поток - QThread (0x18c603e0), текущий поток - QThread (0x18cc3b60) QSocketNotifier: уведомители сокета не могут быть отключены из другого потока Ошибка ASSERT в QCoreApplication :: sendEvent: «Невозможно отправить события объектам в QCoreApplication :: sendEvent: другой поток. Текущий поток 18cc3b60. Receiver "" (типа "QNativeSocketEngine") был создан в потоке 18c603e0 ", файл kernel / qcoreapplication.cpp, строка 420

Я нашел похожие сообщения, но, к сожалению, не мог понять, как исправить свою проблему. Пожалуйста помогите.


person Community    schedule 16.02.2013    source источник


Ответы (1)


Вы не можете использовать QTcpSocket из двух разных потоков, потому что QObject не являются потокобезопасными.

Вы создали свой QTcpSocket в первой задаче, поэтому он «живет» в потоке, связанном с этой задачей. Если вы передадите его указатель в другой QRunnable, то второй поток попытается получить к нему доступ, что приведет к поломке.

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

person JKSH    schedule 17.02.2013
comment
Спасибо за помощь. Перед публикацией проблемы я реализовал свое приложение так, как вы посоветовали. Он работает хорошо, но выглядит ужасно, потому что в будущем я планирую добавить много задач. И если я использую специфический функциональный подход, в какой-то момент моя первоначальная задача станет слишком толстой. Поэтому я решил попросить сообщество дать лучшее решение. - person ; 17.02.2013
comment
Но это достаточно странно. В первой задаче я использую QTcpSocket * socket = new QTcpSocket; Таким образом я хотел перебросить этот сокет между задачами. И я понял. Я читаю первый байт в исходной задаче, создаю другую задачу и передаю ей указатель сокета, после этого ничего не делаю с сокетом - задача завершается. Во второй задаче я отправляю через этот сокет тестовое сообщение клиентскому приложению. Клиент получает это сообщение. Но затем я делаю «удалить сокет»; во втором деструкторе задачи мое приложение вылетает. - person ; 17.02.2013
comment
Даже если вы больше ничего не делаете с сокетом, он все равно обрабатывает события внутри себя. - person JKSH; 17.02.2013
comment
Вот сценарий: (1) В задаче 1 вы создаете QTcpSocket в потоке 1, поэтому он живет в потоке 1. (2) Вы читаете первый байт из QTcpSocket в потоке 1. (3) Вы создаете задачу 2, которая запускается. в потоке 2. (4) Вы передаете указатель сокета потоку 2. (5) Задача 1 завершается. Поток 1 возвращается в пул потоков. (6) Сокет продолжает получать данные, что вызывает внутренние события. Теперь, где будут запускаться обработчики событий? Ответ: Тема 1! Qt запоминает поток, в котором создается объект - это поток, который будет обрабатывать события / сигналы объекта. - person JKSH; 17.02.2013
comment
Более того: теперь, когда поток 1 был возвращен в пул потоков, Qt может использовать его для выполнения других задач. Если это произойдет, этот поток будет запускать вашу новую задачу И обработчики событий сокета. - person JKSH; 17.02.2013
comment
Помните: QRunnable предназначен для выполнения задач в разных потоках. Вы можете создавать свои собственные классы задач, которые не наследуют QRunnable. Выберите класс для создания на основе первого байта и позвольте им работать в том же потоке, что и исходная задача. - person JKSH; 17.02.2013
comment
JKSH, спасибо за ваши объяснения. Они такие ясные. Но у меня есть еще один вопрос. Есть ли возможность передать владение объектом другому потоку? Я читал о методе QObject :: moveToThread (), но, как сказано в руководстве: эта функция может только подталкивать объект из текущего потока в другой поток. А мне нужно наоборот. Это просто для интереса. - person ; 17.02.2013
comment
Теоретически да, вы можете безопасно изменить владельца потока через moveToThread(), если (1) вы можете найти безопасный способ передать указатель QThread из вашего 2-го потока обратно в ваш 1-й поток, и (2) вы можете гарантировать, что 2-й поток не используйте сокет до тех пор, пока 1-й поток не вызовет moveToThread(). Я думаю, что это очень запутанный процесс, который не дает никаких преимуществ в вашем случае :) - person JKSH; 18.02.2013
comment
И последнее: вызывать delete для QObject не рекомендуется - если у него все еще есть события в очереди на обработку, delete вызовет сбой. используйте вместо этого deleteLater(). Удачи! - person JKSH; 18.02.2013