You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/threadmanager.h

440 lines
13 KiB

// 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 "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 Q_OBJECT classes)
class JobBase : public TQObject {
Q_OBJECT
TQ_OBJECT
protected:
JobBase() : TQObject(), m_aborted( false ) {}
public slots:
void abort() { m_aborted = true; }
protected:
bool m_aborted;
};
class 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
{
// Q_OBJECT
// 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
{
// Q_OBJECT
// 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