次の方法でライブオブジェクトを実装しています。バックグラウンドで長いタスクを実行するために使用されます。メインスレッドは、パブリックスロット(つまり、doTask)にシグナルを送信することによってタスクを呼び出します。これは簡略化された例です(テストされていません)。
class MyTask : public QObject
{
Q_OBJECT
public:
MyTask();
~MyTask();
public slots:
void doTask( int param );
private slots:
void stated();
signals:
void taskCompleted( int result );
private:
QThread m_thread;
};
MyTask::MyTask()
{
moveToThread(&m_thread);
connect( &m_thread, SIGNAL(started()), this, SLOT(started()));
m_thread.start();
}
MyTask::~MyTask()
{
// Gracefull thread termination (queued in exec loop)
if( m_thread.isRunning() )
{
m_thread.quit();
m_thread.wait();
}
}
void MyTask::started()
{
// initialize live object
}
void MyTask::doTask( int param )
{
sleep( 10 );
emit taskCompleted( param*2 );
}
これは、doTask()がシグナルによって呼び出される限り、期待どおりに機能するはずです。ただし、メインスレッドがdoTask()を直接呼び出すと、メインスレッドによって実行されます。一部のタスクでは、slotメソッドが直接呼び出された場合でも、ライブオブジェクトのスレッドによる実行を強制したいと思います。
DoTask()の前にコードを追加して、現在のスレッドがm_threadであるかどうかを確認できます。この場合、メソッドが実行されます。そうでない場合は、doTask()が 'this'にシグナルを送信して、doTask()の呼び出しがm_thread execループのキューに入れられ、できるだけ早く実行されるようにします。
どうすればそれができますか?
[〜#〜] edit [〜#〜]:提案された回答に基づいて、新しいコードを次に示します。 doTaskメソッドは、メインスレッドから直接呼び出された場合でも、ライブオブジェクトのスレッドによる実行を委任するようになりました。シグナルによって呼び出されても、期待どおりに機能します。
class MyTask : public QObject
{
Q_OBJECT
public:
explicit MyTask( QObject *parent = 0 );
~MyTask();
public slots:
void doTask( int param );
private slots:
void doTaskImpl( int param );
signals:
void taskCompleted( int result );
private:
QThread m_thread;
};
MyTask::MyTask( QObject *parent) : QObject(parent)
{
moveToThread(&m_thread);
m_thread.start();
}
MyTask::~MyTask()
{
// Gracefull thread termination (queued in exec loop)
if( m_thread.isRunning() )
{
m_thread.quit();
m_thread.wait();
}
}
void MyTask::doTask( int param )
{
QMetaObject::invokeMethod( this, "doTaskImpl", Q_ARG( int, param ) );
}
void MyTask::doTaskImpl( int param )
{
// Do the live oject's asynchronous task
sleep( 10 );
emit taskCompleted( param*2 );
}
これは、別のスレッドで非同期メソッドの実行をサポートするために私が見つけた最も単純な実装です。 doTask()メソッドの呼び出しは、スレッドが開始されるとすぐにキューに入れられ、処理されます。オブジェクトスレッドから呼び出されると、すぐに実行されます(キューに入れられません)。
Start()シグナルは、スレッドが開始されたときにのみ発行されることに注意してください。これは、スレッドが開始される前にキューに入れられたdoTask()メソッドの呼び出しが、started()メソッドスロットが呼び出される前に実行されることを意味します。これが、最初の実装から削除した理由です。したがって、オブジェクトの初期化はコンストラクターで実行することが望ましいです。
呼び出したい QMetaObject::invokeMethod
これを行うには。あなたの場合、それは次のようになります
MyTask *task;
int param;
// ...
// Will automatically change threads, if needed, to execute
// the equivalent of:
// (void)task->doTask( param );
QMetaObject::invokeMethod( task, "doTask", Q_ARG( int, param ) );
私が追加する唯一の改善点は、メソッドの検索にかかる時間を節約することです。
class MyTask {
// ...
private:
int m_doTaskImplIndex;
};
MyTask::MyTask() :
//...
m_doTaskImplIndex(metaObject()->indexOfMethod("doTaskImpl"))
//...
{}
void MyTask::doTask( int param )
{
metaObject()->method(m_doTaskImplIndex).invoke(this, Q_ARG( int, param ) );
}
MyTaskにバグがあると思います。 Qtの内部を正しく理解していれば、
moveToThread(&m_thread);
parent
が0でない場合、失敗します。
では、すべてをニースクラスにまとめてみませんか?
スロットfinishPlease
も追加しました。これは、メッセージToDoリストの最後の要素として追加され、保留中のすべてのメッセージを実際に処理したときにメインプログラムにフィードバックを提供します。殺された。
class Threaded : public QObject
{
Q_OBJECT
public:
Threaded() {
thread = new QThread(this);
this->moveToThread(thread);
connect(thread, SIGNAL(started()), this, SLOT(init()), \
Qt::QueuedConnection);
thread->start();
}
virtual ~Threaded() {
thread->exit();
thread->wait();
delete thread;
}
signals:
void okayKillMe();
public slots:
virtual void init() = 0;
void finishPlease() {emit okayKillMe();}
protected:
QThread* thread;
};
class MyClass : public Threaded
{
Q_OBJECT
public:
MyClass() { }
virtual ~MyClass() { }
public slots:
void init() { }
void doStuff() { }
void doOtherStuff(int* data) { }
};