Russian Qt Forum
Май 15, 2024, 22:47 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Многократный вызов функции из потока  (Прочитано 5038 раз)
voltron
Гость
« : Апрель 19, 2011, 14:34 »

Есть некая функция, выполняющая длительную операцию. Хочу вынести ее в отдельный поток, чтобы на время выполнения не блокировался интерфейс. Т.к. эта функция вызывается несколько раз с разными входными параметрами, то хотелось бы реализовать все так, чтобы можно было создать поток один раз и вызывать функцию по необходимости (в остальное время поток должен спать).

Посмотрел в Qt Assistant, похожий функционал есть в Mandelbrot Example. Попробовал реализовать и вот что получилось

extractorthread.h
Код:
class ExtractorThread : public QThread
{
    Q_OBJECT
  public:
    ExtractorThread( QObject *parent = 0 );
    ~ExtractorThread();
   
    // запускает операцию
    void extractData( vectorData* inLayer, vectorData* outLayer, double* transform );
    // для отмены операции
    void stop();
 
  signals:
    void pointProcessed();          // посылается на каждой итерации для отображения прогресса
    void extractionFinished();      // посылается при успешном завершениии
    void extractionInterrupted(); // посылается, если выполнение прервано пользователем
 
  protected:
    void run();
 
  private:
    bool mStop;
    bool mStart;
    QMutex mMutex;
    QWaitCondition mCondition;

    double* mTransform;
    vectorData* mInputData;
    vectorData* mOutputData;
}

extractorthread.cpp
Код:
ExtractorThread::ExtractorThread( QWidget* parent = 0 )
    : QThread( parent ), mStop( false ), mStart( false )
{
}

ExtractorThread::~ExtractorThread()
{
  mMutex.lock();
  mStop = true;
  mCondition.wakeOne();
  mMutex.unlock();
 
  wait();
}

ExtractorThread::extractData( vectorData* inLayer,   vectorData* outLayer, double* transform )
{
  QMutexLocker locker( &mMutex );
 
  mInputData = inLayer;
  mOutputData = outLayer;
  mTransform = transform;
 
  if ( !isRunning() )
  {
    start();
  }
  else
  {
    mStart = true;
    mCondition.wakeOne();
  }
}

ExtractorThread::stop()
{
  mMutex.lock();
  mStop = true;
  mMutex.unlock();
 
  wait();
}

ExtractorThread::run()
{
  mMutex.lock();
  mStop = false;
  mMutex.unlock();
 
  // продготовка, объявление и инициализация локальны переменных 
  .....
 
  bool s = false;
  bool interrupted = false;

  // основной цикл
  while ( mInputData->next() )
  {
    // выполняем обработку
    .....
    emit( pointProcessed() );
   
    // если пользователь решил прервать операцию
    mMutex.lock();
    s = mStop;
    mMutex.unlock();
       
    if ( s )
    {
      interrupted = true;
      break;
    }
  }
 
  if ( !interrupted )
  {
    emit( extractionFinished() );
  }
  else
  {
    emit( extractionInterrupted() );
  }

  mMutex.lock();
  if ( !mStart )
  {
    mCondition.wait( &mMutex );
  }
  restart = false;
  mMutex.unlock();
}

Правильный ли это подход, не упустил ли я что-либо важное из виду? С многопоточностью практически не работал, поэтому прошу совета.

И еще один вопрос. В функцию передаются указатели на классы-наследники QObject. Нужно ли на время доступа к методам этих классов защищать их мьютексом?
Записан
merke
Гость
« Ответ #1 : Апрель 19, 2011, 14:54 »

В общем тебе нужно следующее.

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

Из этого всего следует, что ни когда не вызывай напрямую функцию в потоке.

Теперь ещё: когда будешь создавать объект своего класса:

Код:
ExtractorThread *myThread = new ExtractorThread();

Делай
Код:
myThread->moveToThread(myThread);
« Последнее редактирование: Апрель 19, 2011, 15:07 от Александр » Записан
mutineer
Гость
« Ответ #2 : Апрель 19, 2011, 15:10 »

Теперь ещё: когда будешь создавать объект своего класса:

Код:
ExtractorThread *myThread = new ExtractorThread();

Делай
Код:
myThread->moveToThread(myThread);

Не стоит мувать тред в самого себя. Уж лучше создать свой класс наследником QObject и перемещать его в обычный QThread
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Апрель 19, 2011, 15:14 »

Есть некая функция, выполняющая длительную операцию. Хочу вынести ее в отдельный поток, чтобы на время выполнения не блокировался интерфейс. Т.к. эта функция вызывается несколько раз с разными входными параметрами, то хотелось бы реализовать все так, чтобы можно было создать поток один раз и вызывать функцию по необходимости (в остальное время поток должен спать).

Посмотрел в Qt Assistant, похожий функционал есть в Mandelbrot Example. Попробовал реализовать и вот что получилось
Здесь не видно никакой необходимости что-то защищать (конфликтов нет), проще сделать как сказал Александр (с учетом moveToThread что многократно здесь обсуждалось). Если главная нитка хочет прервать вычисления, достаточно если она просто взведет mStop. Это также не требует мутексов, просто надо иметь ввиду: рабочая нитка не прервется немедленно, она должна увидеть mStop = true, закончить вычисления и отэмитить сигнал главной

И еще один вопрос. В функцию передаются указатели на классы-наследники QObject. Нужно ли на время доступа к методам этих классов защищать их мьютексом?
Вы собираетесь менять их из главной нитки в процессе вычислений? Нет (судя по Вашему коду). Значит защита не требуется
Записан
voltron
Гость
« Ответ #4 : Апрель 19, 2011, 17:13 »

Создать не функцию в потоке, а слот с необходимыми параметрами.
А у себя уже создай сигнал с такими же параметрами. Свой сигнал свяжи со слотом из потока.
Когда необходимо передать данные потоку емить свой сигнал.  
Аналогичным способом получай данные из потока. Там емить сигнал с результирующими данными. В главном потоке связывай тот сигнал со своим слотом в главном потоке и обрабатывай данные.
Спасибо. Значит у меня должно быть что-то вроде такого, да?
Код:
class ExtractorThread : public QThread
{
    Q_OBJECT
  public:
    ExtractorThread( QObject *parent = 0 );
    ~ExtractorThread();
   
  public slots:
    // запускает операцию
    void extractData( vectorData* inLayer, vectorData* outLayer, double* transform );
    // для отмены операции
    void stop();
И соответсвующие сигналы в основной нитке.

Данные назад в основную нитку передаются косвенным образом, примерно так
Код:
  vectorProvider* provider = outLayer->provider();
  QList<Blocks> data;
  provider->addData( data );
Поэтому сигнал с результирующими данными не нужен, нужно только сигнализировать о завершении или прерывании обработки.

Из этого всего следует, что ни когда не вызывай напрямую функцию в потоке.
Т.е. пример Mandelbrot Example из Assistant, где есть вызов функции из потока не совсем правильный и так лучше не делать?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Апрель 19, 2011, 17:40 »

Код:
  public slots: 
    // запускает операцию
    void extractData( vectorData* inLayer, vectorData* outLayer, double* transform );
    // для отмены операции
    void stop();
И соответсвующие сигналы в основной нитке.
Если Вы сделаете stop через сигнал/слот, то он может быть принят рабочей ниткой только после предыдущего (extractData), т.е. такой stop ничего не прерывает. Поэтому stop надо делать напрямую

Из этого всего следует, что ни когда не вызывай напрямую функцию в потоке.
Т.е. пример Mandelbrot Example из Assistant, где есть вызов функции из потока не совсем правильный и так лучше не делать?
Ну "исключения подтверждают правило"  Улыбающийся
Записан
CL0NE
Гость
« Ответ #6 : Апрель 19, 2011, 20:18 »

Цитировать
Если Вы сделаете stop через сигнал/слот, то он может быть принят рабочей ниткой только после предыдущего (extractData), т.е. такой stop ничего не прерывает. Поэтому stop надо делать напрямую
Дополнение: processEvents/свой eventloop в extractData, тогда можно и слотом
« Последнее редактирование: Апрель 19, 2011, 20:21 от CL0NE » Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.151 секунд. Запросов: 19.