|
|
|
// Author: Max Howell (C) Copyright 2004
|
|
|
|
// (c) 2005 Jeff Mitchell <kde-dev@emailgoeshere.com>
|
|
|
|
// See COPYING file that comes with this distribution
|
|
|
|
//
|
|
|
|
|
|
|
|
#ifndef THREADMANAGER_H
|
|
|
|
#define THREADMANAGER_H
|
|
|
|
|
|
|
|
#include "amarok_export.h"
|
|
|
|
|
|
|
|
#include "debug.h"
|
|
|
|
#include <tqevent.h> //baseclass
|
|
|
|
#include <tqguardedptr.h>
|
|
|
|
#include <tqmap.h>
|
|
|
|
#include <tqobject.h>
|
|
|
|
#include <tqthread.h>
|
|
|
|
#include <tqvaluelist.h>
|
|
|
|
#include <tqmutex.h>
|
|
|
|
#include "debug.h"
|
|
|
|
|
|
|
|
#define DISABLE_GENERATED_MEMBER_FUNCTIONS_3( T ) \
|
|
|
|
T( const T& ); \
|
|
|
|
T &operator=( const T& ); \
|
|
|
|
bool operator==( const T& ) const;
|
|
|
|
|
|
|
|
#define DISABLE_GENERATED_MEMBER_FUNCTIONS_4( T ) \
|
|
|
|
T(); \
|
|
|
|
DISABLE_GENERATED_MEMBER_FUNCTIONS_3( T )
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @class ThreadManager
|
|
|
|
* @author Max Howell <max.howell@methylblue.com>
|
|
|
|
* @short ThreadManager is designed to encourage you to use threads and to make their use easy.
|
|
|
|
*
|
|
|
|
* You create Jobs on the heap and ThreadManager allows you to easily queue them,
|
|
|
|
* abort them, ensure only one runs at once, ensure that bad data is never acted
|
|
|
|
* on and even cleans up for you should the class that wants the Job's results
|
|
|
|
* get deleted while a thread is running.
|
|
|
|
*
|
|
|
|
* You also will (soon) get thread-safe error handling and thread-safe progress
|
|
|
|
* reporting.
|
|
|
|
*
|
|
|
|
* This is a typical use:
|
|
|
|
*
|
|
|
|
class MyJob : public ThreadManager::Job
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
MyJob( TQObject *dependent ) : Job( dependent, "MyJob" ) {}
|
|
|
|
|
|
|
|
virtual bool doJob() {
|
|
|
|
//do some work in thread...
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void completeJob {
|
|
|
|
//do completion work in the GUI thread...
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
SomeClass::someFunction()
|
|
|
|
{
|
|
|
|
ThreadManager::instance()->queueJob( new MyJob( this ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
*
|
|
|
|
* That's it! The queue is fifo, there's one queue per job-type, the
|
|
|
|
* ThreadManager takes ownership of the Job, and the Manager calls
|
|
|
|
* Job::completeJob() on completion which you reimplement to do whatever you
|
|
|
|
* need done.
|
|
|
|
*
|
|
|
|
* BEWARE! None of the functions are thread-safe, only call them from the GUI
|
|
|
|
* thread or your application WILL crash!
|
|
|
|
*
|
|
|
|
* @see ThreadManager::Job
|
|
|
|
* @see ThreadManager::DependentJob
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/// This class is because moc "is really good" (no nested TQ_OBJECT classes)
|
|
|
|
class LIBAMAROK_EXPORT JobBase : public TQObject {
|
|
|
|
TQ_OBJECT
|
|
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
JobBase() : TQObject(), m_aborted( false ) {}
|
|
|
|
public slots:
|
|
|
|
void abort() { m_aborted = true; }
|
|
|
|
protected:
|
|
|
|
bool m_aborted;
|
|
|
|
};
|
|
|
|
|
|
|
|
class LIBAMAROK_EXPORT ThreadManager : public TQObject
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
class Thread;
|
|
|
|
friend class Thread;
|
|
|
|
typedef TQValueList<Thread*> ThreadList;
|
|
|
|
class Job;
|
|
|
|
friend class Job;
|
|
|
|
typedef TQValueList<Job*> JobList;
|
|
|
|
|
|
|
|
static ThreadManager *instance();
|
|
|
|
static void deleteInstance();
|
|
|
|
|
|
|
|
static volatile uint getNewThreadId();
|
|
|
|
static TQMutex *threadIdMutex;
|
|
|
|
static volatile uint threadIdCounter;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the ThreadManager is already handling a job of this type then the job
|
|
|
|
* will be queued, otherwise the job will be processed immediately. Allocate
|
|
|
|
* the job on the heap, and ThreadManager will delete it for you.
|
|
|
|
*
|
|
|
|
* This is not thread-safe - only call it from the GUI-thread!
|
|
|
|
*
|
|
|
|
* @return number of jobs in the queue after the call
|
|
|
|
* @see ThreadManager::Job
|
|
|
|
*/
|
|
|
|
int queueJob( Job* );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queue multiple jobs simultaneously, you should use this to avoid the race
|
|
|
|
* condition where the first job finishes before you can queue the next one.
|
|
|
|
* This isn't a fatal condition, but it does cause wasteful thread deletion
|
|
|
|
* and re-creation. The only valid usage, is when the jobs are the same type!
|
|
|
|
*
|
|
|
|
* This is not thread-safe - only call it from the GUI-thread!
|
|
|
|
*
|
|
|
|
* @return number of jobs in the queue after the call
|
|
|
|
*/
|
|
|
|
int queueJobs( const JobList& );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If there are other jobs of the same type running, they will be aborted,
|
|
|
|
* then this one will be started afterwards. Aborted jobs will not have
|
|
|
|
* completeJob() called for them.
|
|
|
|
*
|
|
|
|
* This is not thread-safe - only call it from the GUI-thread!
|
|
|
|
*/
|
|
|
|
void onlyOneJob( Job* );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* All the named jobs will be halted and deleted. You cannot use any data
|
|
|
|
* from the jobs reliably after this point. Job::completeJob() will not be
|
|
|
|
* called for any of these jobs.
|
|
|
|
*
|
|
|
|
* This is not thread-safe - only call it from the GUI-thread!
|
|
|
|
*
|
|
|
|
* @return how many jobs were aborted, or -1 if no thread was found
|
|
|
|
*/
|
|
|
|
int abortAllJobsNamed( const TQCString &name );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return true if a Job with name is queued or is running
|
|
|
|
*/
|
|
|
|
bool isJobPending( const TQCString &name ) { return jobCount( name ) > 0; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the number of jobs running, pending, aborted and otherwise.
|
|
|
|
*/
|
|
|
|
uint jobCount( const TQCString &name );
|
|
|
|
|
|
|
|
private:
|
|
|
|
ThreadManager();
|
|
|
|
~ThreadManager();
|
|
|
|
|
|
|
|
enum EventType { JobEvent = 20202, OverrideCursorEvent, RestoreOverrideCursorEvent };
|
|
|
|
|
|
|
|
virtual bool event( TQEvent* );
|
|
|
|
|
|
|
|
/// checks the pool for an available thread, creates a new one if required
|
|
|
|
Thread *gimmeThread();
|
|
|
|
|
|
|
|
/// safe disposal for threads that may not have finished
|
|
|
|
void dispose( Thread* );
|
|
|
|
|
|
|
|
/// all pending and running jobs
|
|
|
|
JobList m_jobs;
|
|
|
|
|
|
|
|
/// a thread-pool, ready for use or running jobs currently
|
|
|
|
ThreadList m_threads;
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
* Class Thread
|
|
|
|
*/
|
|
|
|
class Thread : public TQThread
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Thread();
|
|
|
|
|
|
|
|
virtual void run();
|
|
|
|
|
|
|
|
void runJob( Job* );
|
|
|
|
void msleep( int ms ) { TQThread::msleep( ms ); } //we need to make this public for class Job
|
|
|
|
|
|
|
|
Job *job() const { return m_job; }
|
|
|
|
|
|
|
|
static TQThread* getRunning();
|
|
|
|
static TQString threadId();
|
|
|
|
const uint localThreadId() const { return m_threadId; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
Job *m_job;
|
|
|
|
|
|
|
|
uint m_threadId;
|
|
|
|
|
|
|
|
//private so I don't break something in the distant future
|
|
|
|
~Thread();
|
|
|
|
|
|
|
|
//we can delete threads here only
|
|
|
|
friend bool ThreadManager::event( TQEvent* );
|
|
|
|
|
|
|
|
protected:
|
|
|
|
DISABLE_GENERATED_MEMBER_FUNCTIONS_3( Thread )
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @class Job
|
|
|
|
* @short A small class for doing work in a background thread
|
|
|
|
*
|
|
|
|
* Derive a job, do the work in doJob(), do GUI-safe operations in
|
|
|
|
* completeJob(). If you return false from doJob() completeJob() won't be
|
|
|
|
* called. Name your Job well as like-named Jobs are queued together.
|
|
|
|
*
|
|
|
|
* Be sensible and pass data members to the Job, rather than operate on
|
|
|
|
* volatile data members in the GUI-thread.
|
|
|
|
*
|
|
|
|
* Things can change while you are in a separate thread. Stuff in the GUI
|
|
|
|
* thread may not be there anymore by the time you finish the job. @see
|
|
|
|
* ThreadManager::dependentJob for a solution.
|
|
|
|
*
|
|
|
|
* Do your cleanup in the destructor not completeJob(), as completeJob()
|
|
|
|
* doesn't have to be called.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef Q_MOC_RUN
|
|
|
|
|
|
|
|
class Job : public JobBase, public TQCustomEvent
|
|
|
|
{
|
|
|
|
// TQ_OBJECT
|
|
|
|
//
|
|
|
|
friend class ThreadManager; //access to m_thread
|
|
|
|
friend class ThreadManager::Thread; //access to m_aborted
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
* Like-named jobs are queued and run FIFO. Always allocate Jobs on the
|
|
|
|
* heap, ThreadManager will take ownership of the memory.
|
|
|
|
*/
|
|
|
|
Job( const char *name );
|
|
|
|
virtual ~Job();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* These are used by @class DependentJob, but are made available for
|
|
|
|
* your use should you need them.
|
|
|
|
*/
|
|
|
|
enum EventType { JobFinishedEvent = ThreadManager::JobEvent, JobStartedEvent };
|
|
|
|
|
|
|
|
const char *name() const { return m_name; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If this returns true then in the worst case the entire Amarok UI is
|
|
|
|
* frozen waiting for your Job to abort! You should check for this
|
|
|
|
* often, but not so often that your code's readability suffers as a
|
|
|
|
* result.
|
|
|
|
*
|
|
|
|
* Aborted jobs will not have completeJob() called for them, even if
|
|
|
|
* they return true from doJob()
|
|
|
|
*/
|
|
|
|
bool isAborted() const { return m_aborted; }
|
|
|
|
|
|
|
|
///convenience function
|
|
|
|
bool wasSuccessful() const { return !m_aborted; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls TQThread::msleep( int )
|
|
|
|
*/
|
|
|
|
void msleep( int ms ) { m_thread->msleep( ms ); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* You should set @param description if you set progress information
|
|
|
|
* do this in the ctor, or it won't have an effect
|
|
|
|
*/
|
|
|
|
void setDescription( const TQString &description ) { m_description = description; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If you set progress information, you should set this too, changing it when appropriate
|
|
|
|
*/
|
|
|
|
void setStatus( const TQString &status );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This shows the progressBar too, the user will be able to abort
|
|
|
|
* the thread
|
|
|
|
*/
|
|
|
|
void setProgressTotalSteps( uint steps );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Does a thread-safe update of the progressBar
|
|
|
|
*/
|
|
|
|
void setProgress( uint progress );
|
|
|
|
void setProgress100Percent() { setProgress( m_totalSteps ); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience function, increments the progress by 1
|
|
|
|
*/
|
|
|
|
void incrementProgress();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sometimes you want to hide the progressBar etc. generally you
|
|
|
|
* should show one, but perhaps you are a reimplemented class
|
|
|
|
* that doesn't want one?
|
|
|
|
*/
|
|
|
|
//void setVisible( bool );
|
|
|
|
|
|
|
|
uint parentThreadId() { return m_parentThreadId; }
|
|
|
|
|
|
|
|
protected:
|
|
|
|
/**
|
|
|
|
* Executed inside the thread, this should be reimplemented to do the
|
|
|
|
* job's work. Be thread-safe! Don't interact with the GUI-thread.
|
|
|
|
*
|
|
|
|
* @return true if you want completeJob() to be called from the GUI
|
|
|
|
* thread
|
|
|
|
*/
|
|
|
|
virtual bool doJob() = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is executed in the GUI thread if doJob() returns true;
|
|
|
|
*/
|
|
|
|
virtual void completeJob() = 0;
|
|
|
|
|
|
|
|
/// be sure to call the base function in your reimplementation
|
|
|
|
virtual void customEvent( TQCustomEvent* );
|
|
|
|
|
|
|
|
private:
|
|
|
|
char const * const m_name;
|
|
|
|
Thread *m_thread;
|
|
|
|
|
|
|
|
protected: //FIXME
|
|
|
|
uint m_percentDone;
|
|
|
|
uint m_progressDone;
|
|
|
|
uint m_totalSteps;
|
|
|
|
uint m_parentThreadId;
|
|
|
|
|
|
|
|
TQString m_description;
|
|
|
|
TQString m_status;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
DISABLE_GENERATED_MEMBER_FUNCTIONS_4( Job )
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @class DependentJob
|
|
|
|
* @short A Job that depends on the existence of a TQObject
|
|
|
|
*
|
|
|
|
* This Job type is dependent on a TQObject instance, if that instance is
|
|
|
|
* deleted, this Job will be aborted and safely deleted.
|
|
|
|
*
|
|
|
|
* ThreadManager::DependentJob (and Job, the baseclass) isa TQCustomEvent,
|
|
|
|
* and completeJob() is reimplemented to send the job to the dependent.
|
|
|
|
* Of course you can still reimplement completeJob() yourself.
|
|
|
|
*
|
|
|
|
* The dependent will receive a JobStartedEvent just after the creation of
|
|
|
|
* the Job (not after it has started unfortunately), and a JobFinishedEvent
|
|
|
|
* after the Job has finished.
|
|
|
|
*
|
|
|
|
* The dependent is a TQGuardedPtr, so you can reference the pointer returned
|
|
|
|
* from dependent() safely provided you always test for 0 first. However
|
|
|
|
* safest of all is to not rely on that pointer at all! Pass required
|
|
|
|
* data-members with the job, only operate on the dependent in
|
|
|
|
* completeJob(). completeJob() will not be called if the dependent no
|
|
|
|
* longer exists
|
|
|
|
*
|
|
|
|
* It is only safe to have one dependent, if you depend on multiple objects
|
|
|
|
* that might get deleted while you are running you should instead try to
|
|
|
|
* make the multiple objects children of one TQObject and depend on the
|
|
|
|
* top-most parent or best of all would be to make copies of the data you
|
|
|
|
* need instead of being dependent.
|
|
|
|
*/
|
|
|
|
|
|
|
|
class DependentJob : public Job
|
|
|
|
{
|
|
|
|
// TQ_OBJECT
|
|
|
|
//
|
|
|
|
public:
|
|
|
|
DependentJob( TQObject *dependent, const char *name );
|
|
|
|
|
|
|
|
virtual void completeJob();
|
|
|
|
|
|
|
|
TQObject *dependent() { return m_dependent; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
const TQGuardedPtr<TQObject> m_dependent;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
DISABLE_GENERATED_MEMBER_FUNCTIONS_4( DependentJob )
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif // Q_MOC_RUN
|
|
|
|
|
|
|
|
protected:
|
|
|
|
ThreadManager( const ThreadManager& );
|
|
|
|
ThreadManager &operator=( const ThreadManager& );
|
|
|
|
};
|
|
|
|
|
|
|
|
//useful debug thingy
|
|
|
|
#define DEBUG_THREAD_FUNC_INFO { Debug::mutex.lock(); kdDebug() << Debug::indent() << k_funcinfo << "thread: " << ThreadManager::Thread::threadId() << endl; Debug::mutex.unlock(); }
|
|
|
|
|
|
|
|
#define SHOULD_BE_GUI if( ThreadManager::Thread::getRunning() ) warning() \
|
|
|
|
<< __PRETTY_FUNCTION__ << " should not be Threaded, but is running in " << \
|
|
|
|
ThreadManager::Thread::getRunning() <<endl;
|
|
|
|
|
|
|
|
inline ThreadManager*
|
|
|
|
ThreadManager::instance()
|
|
|
|
{
|
|
|
|
static ThreadManager* instance = new ThreadManager();
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void
|
|
|
|
ThreadManager::deleteInstance()
|
|
|
|
{
|
|
|
|
delete instance();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline volatile uint
|
|
|
|
ThreadManager::getNewThreadId()
|
|
|
|
{
|
|
|
|
uint temp;
|
|
|
|
threadIdMutex->lock();
|
|
|
|
temp = threadIdCounter++;
|
|
|
|
threadIdMutex->unlock();
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|