worked on thread safety with tasks

This commit is contained in:
itsmattkc
2019-08-09 12:31:02 +10:00
parent c0f6ee534a
commit 1616166fbc
9 changed files with 137 additions and 27 deletions
+2
View File
@@ -26,6 +26,8 @@ set(OLIVE_SOURCES
common/qobjectlistcast.h
common/qtversionabstraction.h
common/qtversionabstraction.cpp
common/threadedobject.h
common/threadedobject.cpp
common/timecodefunctions.h
common/timecodefunctions.cpp
PARENT_SCOPE
+37
View File
@@ -0,0 +1,37 @@
#include "threadedobject.h"
ThreadedObject::ThreadedObject()
{
}
void ThreadedObject::LockDeletes()
{
threadobj_delete_lock_++;
}
void ThreadedObject::UnlockDeletes()
{
Q_ASSERT(AreDeletesLocked());
threadobj_delete_lock_--;
}
bool ThreadedObject::AreDeletesLocked()
{
return (threadobj_delete_lock_ > 0);
}
void ThreadedObject::LockMutex()
{
threadobj_main_lock_.lock();
}
void ThreadedObject::UnlockMutex()
{
threadobj_main_lock_.unlock();
}
bool ThreadedObject::TryLockMutex(int timeout)
{
return threadobj_main_lock_.tryLock(timeout);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef THREADEDOBJECT_H
#define THREADEDOBJECT_H
#include <QMutex>
class ThreadedObject
{
public:
ThreadedObject();
void LockMutex();
void UnlockMutex();
bool TryLockMutex(int timeout = 0);
void LockDeletes();
void UnlockDeletes();
bool AreDeletesLocked();
private:
QMutex threadobj_main_lock_;
QAtomicInt threadobj_delete_lock_;
};
#endif // THREADEDOBJECT_H
-10
View File
@@ -157,13 +157,3 @@ bool Item::ChildExistsWithNameInternal(const QString &name, Item *folder)
return false;
}
void Item::Lock()
{
mutex_.lock();
}
void Item::Unlock()
{
mutex_.unlock();
}
+3 -6
View File
@@ -27,6 +27,8 @@
#include <QMutex>
#include <QString>
#include "common/threadedobject.h"
class Item;
using ItemPtr = std::shared_ptr<Item>;
@@ -36,7 +38,7 @@ using ItemPtr = std::shared_ptr<Item>;
* Project objects implement a parent-child hierarchy of Items that can be used throughout the Project. The Item class
* itself is abstract and will need to be subclassed to be used in a Project.
*/
class Item
class Item : public ThreadedObject
{
public:
enum Type {
@@ -100,9 +102,6 @@ public:
bool ChildExistsWithName(const QString& name);
void Lock();
void Unlock();
private:
bool ChildExistsWithNameInternal(const QString& name, Item* folder);
@@ -116,8 +115,6 @@ private:
QString tooltip_;
QMutex mutex_;
};
#endif // ITEM_H
+6 -5
View File
@@ -41,11 +41,13 @@ ImportTask::ImportTask(ProjectViewModel *model, Folder *parent, const QStringLis
urls_(urls),
parent_(parent)
{
set_text(tr("Importing %1 files").arg (urls.size()));
set_text(tr("Importing %1 files").arg(urls.size()));
}
bool ImportTask::Action()
{
parent_->LockDeletes();
QUndoCommand* command = new QUndoCommand();
Import(urls_, parent_, command);
@@ -54,12 +56,11 @@ bool ImportTask::Action()
// the undo command executes the final import anyway)
if (cancelled()) {
delete command;
return true;
} else {
olive::undo_stack.push(command);
}
// FIXME: This should be run in the main thread somehow
olive::undo_stack.push(command);
parent_->UnlockDeletes();
return true;
}
+2 -2
View File
@@ -34,11 +34,11 @@ ProbeTask::ProbeTask(FootagePtr footage) :
bool ProbeTask::Action()
{
footage_->Lock();
footage_->LockDeletes();
Decoder::ProbeMedia(footage_.get());
footage_->Unlock();
footage_->UnlockDeletes();
return true;
}
+25 -1
View File
@@ -58,6 +58,12 @@ bool Task::Start()
cancelled_ = false;
// Run Prologue() function
if (!Prologue()) {
set_status(kError);
return false;
}
set_status(kWorking);
thread_.start();
@@ -65,11 +71,21 @@ bool Task::Start()
return true;
}
bool Task::Prologue()
{
return true;
}
bool Task::Action()
{
return true;
}
bool Task::Epilogue()
{
return true;
}
const Task::Status &Task::status()
{
return status_;
@@ -151,8 +167,16 @@ void Task::set_status(const Task::Status &status)
void Task::ThreadComplete()
{
// thread_.result() will be set to the return value of Action()
bool succeeded = thread_.result();
if (thread_.result()) {
// Run the Prologue() function for any final tasks
// User cancelling is not considered an error, so we need to check it too
if (succeeded && !cancelled()) {
succeeded = Epilogue();
}
// If everything succeeded, we set the status accordingly
if (succeeded) {
set_status(kFinished);
} else {
set_status(kError);
+37 -3
View File
@@ -89,10 +89,26 @@ public:
bool Start();
/**
* @brief The main Task function
* @brief Perform opening tasks before main Task thread begins
*
* Action() is the function that gets called once another thread has been created. This function should be overridden
* in subclasses.
* If a Task needs to perform any actions in the main thread before starting the Task's thread, (e.g. copying or
* altering information) this function should be overridden and those actions should be performed here. It's
* guaranteed that Prologue() will run in the main thread, and as such, functions here should remain as minimal as
* possible as to not block the main thread for a noticeable amount of time. If your Task does not need any such
* actions, you don't need to override this.
*
* @return
*
* TRUE if the prologue was successful and we can start the Task now. If Prologue returns FALSE, the thread is never
* created and Action()/Epilogue() are never run.
*/
virtual bool Prologue();
/**
* @brief The main Task function which is run in a separate thread
*
* Action() is the function that gets called once the separate thread has been created. This function should be
* overridden in subclasses.
*
* It's also recommended to emit ProgressChanged() throughout your Action() so that any attached ProgressBars can
* show accurate progress information.
@@ -105,6 +121,24 @@ public:
*/
virtual bool Action();
/**
* @brief Perform any closing Tasks in the main thread after the Task thread finishes
*
* It's likely your Task modifies data used throughout the program in some way, and to prevent race conditions, it's
* recommended to work with "copies" of that data in Action() (which is run in separate thread) and never
* access/modify any data used in other threads. Then, after the Action() thread is complete, that data can be used to
* "apply" that data in the main thread here.
*
* As this runs in the main thread, these functions shouldn't be kept fairly minimal to prevent blocking the main
* thread.
*
* @return
*
* TRUE if the Epilogue completed successfully. FALSE if not. A FALSE result here is considered a complete failure
* of the Task, even though the bulk of the processing has been performed in Action().
*/
virtual bool Epilogue();
/**
* @brief Current status of the Task
*