1 // Author: Max Howell (C) Copyright 2004
2 // (c) 2005 Jeff Mitchell <kde-dev@emailgoeshere.com>
3 // See COPYING file that comes with this distribution
6 #ifndef THREADMANAGER_H
7 #define THREADMANAGER_H
9 #include "amarok_export.h"
11 #include <q3valuelist.h>
13 #include <QEvent> //baseclass
20 #define DISABLE_GENERATED_MEMBER_FUNCTIONS_3( T ) \
22 T &operator=( const T& ); \
23 bool operator==( const T& ) const;
25 #define DISABLE_GENERATED_MEMBER_FUNCTIONS_4( T ) \
27 DISABLE_GENERATED_MEMBER_FUNCTIONS_3( T )
31 * @class ThreadManager
32 * @author Max Howell <max.howell@methylblue.com>
33 * @short ThreadManager is designed to encourage you to use threads and to make their use easy.
35 * You create Jobs on the heap and ThreadManager allows you to easily queue them,
36 * abort them, ensure only one runs at once, ensure that bad data is never acted
37 * on and even cleans up for you should the class that wants the Job's results
38 * get deleted while a thread is running.
40 * You also will (soon) get thread-safe error handling and thread-safe progress
43 * This is a typical use:
45 class MyJob : public ThreadManager::Job
48 MyJob( QObject *dependent ) : Job( dependent, "MyJob" ) {}
50 virtual bool doJob() {
51 //do some work in thread...
56 virtual void completeJob {
57 //do completion work in the GUI thread...
61 SomeClass::someFunction()
63 ThreadManager::instance()->queueJob( new MyJob( this ) );
67 * That's it! The queue is fifo, there's one queue per job-type, the
68 * ThreadManager takes ownership of the Job, and the Manager calls
69 * Job::completeJob() on completion which you reimplement to do whatever you
72 * BEWARE! None of the functions are thread-safe, only call them from the GUI
73 * thread or your application WILL crash!
75 * @see ThreadManager::Job
76 * @see ThreadManager::DependentJob
80 /// This class is because moc "is really good" (no nested Q_OBJECT classes)
81 class AMAROK_EXPORT JobBase
: public QObject
{
83 protected: JobBase() : QObject(), m_aborted( false ) {}
84 public slots
: void abort() { m_aborted
= true; }
85 protected: bool m_aborted
;
88 class ThreadManager
: public QObject
94 typedef Q3ValueList
<Thread
*> ThreadList
;
97 typedef Q3ValueList
<Job
*> JobList
;
99 AMAROK_EXPORT
static ThreadManager
*instance();
100 static void deleteInstance();
103 * If the ThreadManager is already handling a job of this type then the job
104 * will be queued, otherwise the job will be processed immediately. Allocate
105 * the job on the heap, and ThreadManager will delete it for you.
107 * This is not thread-safe - only call it from the GUI-thread!
109 * @return number of jobs in the queue after the call
110 * @see ThreadManager::Job
112 AMAROK_EXPORT
int queueJob( Job
* );
115 * Queue multiple jobs simultaneously, you should use this to avoid the race
116 * condition where the first job finishes before you can queue the next one.
117 * This isn't a fatal condition, but it does cause wasteful thread deletion
118 * and re-creation. The only valid usage, is when the jobs are the same type!
120 * This is not thread-safe - only call it from the GUI-thread!
122 * @return number of jobs in the queue after the call
124 int queueJobs( const JobList
& );
127 * If there are other jobs of the same type running, they will be aborted,
128 * then this one will be started afterwards. Aborted jobs will not have
129 * completeJob() called for them.
131 * This is not thread-safe - only call it from the GUI-thread!
133 void onlyOneJob( Job
* );
136 * All the named jobs will be halted and deleted. You cannot use any data
137 * from the jobs reliably after this point. Job::completeJob() will not be
138 * called for any of these jobs.
140 * This is not thread-safe - only call it from the GUI-thread!
142 * @return how many jobs were aborted, or -1 if no thread was found
144 int abortAllJobsNamed( const QByteArray
&name
);
147 * @return true if a Job with name is queued or is running
149 bool isJobPending( const QByteArray
&name
) { return jobCount( name
) > 0; }
152 * @return the number of jobs running, pending, aborted and otherwise.
154 uint
jobCount( const QByteArray
&name
);
160 enum EventType
{ JobEventType
= 20202, OverrideCursorEventType
, RestoreOverrideCursorEventType
};
162 virtual bool event( QEvent
* );
164 /// checks the pool for an available thread, creates a new one if required
165 Thread
*gimmeThread();
167 /// safe disposal for threads that may not have finished
168 void dispose( Thread
* );
170 /// all pending and running jobs
173 /// a thread-pool, ready for use or running jobs currently
174 ThreadList m_threads
;
180 class Thread
: public QThread
188 void msleep( int ms
) { QThread::msleep( ms
); } //we need to make this public for class Job
190 Job
*job() const { return m_job
; }
195 //private so I don't break something in the distant future
198 //we can delete threads here only
199 friend bool ThreadManager::event( QEvent
* );
202 DISABLE_GENERATED_MEMBER_FUNCTIONS_3( Thread
)
207 * @short A small class for doing work in a background thread
209 * Derive a job, do the work in doJob(), do GUI-safe operations in
210 * completeJob(). If you return false from doJob() completeJob() won't be
211 * called. Name your Job well as like-named Jobs are queued together.
213 * Be sensible and pass data members to the Job, rather than operate on
214 * volatile data members in the GUI-thread.
216 * Things can change while you are in a separate thread. Stuff in the GUI
217 * thread may not be there anymore by the time you finish the job. @see
218 * ThreadManager::dependentJob for a solution.
220 * Do your cleanup in the destructor not completeJob(), as completeJob()
221 * doesn't have to be called.
224 class AMAROK_EXPORT Job
: public JobBase
, public QEvent
226 friend class ThreadManager
; //access to m_thread
227 friend class ThreadManager::Thread
; //access to m_aborted
231 * Like-named jobs are queued and run FIFO. Always allocate Jobs on the
232 * heap, ThreadManager will take ownership of the memory.
234 Job( const char *name
);
238 * These are used by @class DependentJob, but are made available for
239 * your use should you need them.
241 enum EventType
{ JobFinishedEventType
= ThreadManager::JobEventType
, JobStartedEventType
};
243 const char *name() const { return m_name
; }
246 * If this returns true then in the worst case the entire Amarok UI is
247 * frozen waiting for your Job to abort! You should check for this
248 * often, but not so often that your code's readability suffers as a
251 * Aborted jobs will not have completeJob() called for them, even if
252 * they return true from doJob()
254 bool isAborted() const { return m_aborted
; }
256 ///convenience function
257 bool wasSuccessful() const { return !m_aborted
; }
260 * Calls QThread::msleep( int )
262 void msleep( int ms
) { m_thread
->msleep( ms
); }
265 * You should set @param description if you set progress information
266 * do this in the ctor, or it won't have an effect
268 void setDescription( const QString
&description
) { m_description
= description
; }
271 * If you set progress information, you should set this too, changing it when appropriate
273 void setStatus( const QString
&status
);
276 * This shows the progressBar too, the user will be able to abort
279 void setProgressTotalSteps( uint steps
);
282 * Does a thread-safe update of the progressBar
284 void setProgress( uint progress
);
285 void setProgress100Percent() { setProgress( m_totalSteps
); }
288 * Convenience function, increments the progress by 1
290 void incrementProgress();
293 * Sometimes you want to hide the progressBar etc. generally you
294 * should show one, but perhaps you are a reimplemented class
295 * that doesn't want one?
297 //void setVisible( bool );
301 * Executed inside the thread, this should be reimplemented to do the
302 * job's work. Be thread-safe! Don't interact with the GUI-thread.
304 * @return true if you want completeJob() to be called from the GUI
307 virtual bool doJob() = 0;
310 * This is executed in the GUI thread if doJob() returns true;
312 virtual void completeJob() = 0;
314 /// be sure to call the base function in your reimplementation
315 virtual void customEvent( QEvent
* );
318 char const * const m_name
;
326 QString m_description
;
330 DISABLE_GENERATED_MEMBER_FUNCTIONS_4( Job
)
335 * @class DependentJob
336 * @short A Job that depends on the existence of a QObject
338 * This Job type is dependent on a QObject instance, if that instance is
339 * deleted, this Job will be aborted and safely deleted.
341 * ThreadManager::DependentJob (and Job, the baseclass) isa QEvent,
342 * and completeJob() is reimplemented to send the job to the dependent.
343 * Of course you can still reimplement completeJob() yourself.
345 * The dependent will receive a JobStartedEvent just after the creation of
346 * the Job (not after it has started unfortunately), and a JobFinishedEventType
347 * after the Job has finished.
349 * The dependent is a QPointer, so you can reference the pointer returned
350 * from dependent() safely provided you always test for 0 first. However
351 * safest of all is to not rely on that pointer at all! Pass required
352 * data-members with the job, only operate on the dependent in
353 * completeJob(). completeJob() will not be called if the dependent no
356 * It is only safe to have one dependent, if you depend on multiple objects
357 * that might get deleted while you are running you should instead try to
358 * make the multiple objects children of one QObject and depend on the
359 * top-most parent or best of all would be to make copies of the data you
360 * need instead of being dependent.
363 class AMAROK_EXPORT DependentJob
: public Job
366 DependentJob( QObject
*dependent
, const char *name
);
368 virtual void completeJob();
370 QObject
*dependent() { return m_dependent
; }
373 const QPointer
<QObject
> m_dependent
;
376 DISABLE_GENERATED_MEMBER_FUNCTIONS_4( DependentJob
)
380 ThreadManager( const ThreadManager
& );
381 ThreadManager
&operator=( const ThreadManager
& );
383 class JobCompletedEvent
;
386 //useful debug thingy
387 #define DEBUG_THREAD_FUNC_INFO kDebug() << Debug::indent() << "thread: " << long( QThread::currentThread() );
389 #define SHOULD_BE_GUI if( QThread::currentThread() != QCoreApplication::instance()->thread() ) std::cout \
390 << "Should not be Threaded, but is running in" << \
391 long (QThread::currentThread()) <<std::endl;
394 inline ThreadManager
*
395 ThreadManager::instance()
397 static ThreadManager
* instance
= new ThreadManager();
403 ThreadManager::deleteInstance()