我正在努力解决我的设计困境。
ClassWithLongOperation
{
Run()
{
RecrusiveOperation();
}
RecrusiveOperation()
{
/* RECURSION */
}
}
MyThread
{
ClassWithLongOperation Op1(10);
Op1.Run(); // Takes several minutes.
ClassWithLongOperation Op2(20);
Op2.Run();
SomeOtherClassWithLongOperation Op3;
Op3.Run();
// Do some other stuff
}
GUI启动 MyThread ,运行时间为5-6分钟。我想在我的GUI上有一个大胖取消按钮,因此用户可以取消操作。
我可以创建一个全局布尔变量 bCancelled ,并检查它是否已在 RecursiveOperation 中设置,但我想成为一个好的C ++& OO程序员并避免全局变量。特别是如果它们必须分布在多个文件中。
那么我(按照良好的设计)如何安全地取消 MyThread ?我可以在设置中更改哪些内容以允许此操作?
我也在使用_beginthreadex
来启动线程,但如果能够提供更简单的解决方案,我可以使用boost。
答案 0 :(得分:4)
您的旗帜不需要是整个程序的全局,但它需要在您的类代码中可见。将标志创建为私有实例成员,将公共函数创建为false / true。在递归函数中,测试其值以验证任务是否应该继续。如果需要,可以将其值设置为false(通过当然的功能)来停止递归调用,即,当用户单击按钮时,您可以在所需的实例中调用该函数。这样你就不会破坏任何OO原则,因为你有一个私有标志和一个公共成员函数来安全地改变它。
答案 1 :(得分:3)
使用全局变量实际上并不是世界上最糟糕的事情。拥有不必要的全局变量会导致维护噩梦,但实际上这听起来像是一个快速且易于理解的解决方案。但是如果你想要一个干净的OO解决方案,这肯定是可能的:
编辑我的原始帖子忽略了您希望能够按顺序运行多个操作的事实,如果其中任何一个操作被取消,则不会执行任何剩余的操作。这意味着将bool
标志保留在取消器内更有用,而不是在每个可取消操作中单独保存;和异常是处理实际控制流的最好方法。我还收紧了一些东西(为标志本身添加volatile
,使名称更清晰,限制了不必要的访问权限。)
// A thing that can cancel another thing by setting a bool to true.
class Canceller {
public:
Canceller : cancelledFlag(false) {}
void RegisterCancellee(Cancellee const& c) {
c.RegisterCanceller(cancelledFlag);
}
void Cancel() {
cancelledFlag = true;
}
private:
volatile bool cancelledFlag;
};
class CancelButton : public Canceller {
...
// Call Cancel() from on-click event handler
...
};
class Cancellation : public std::exception {
public:
virtual const char* what() const throw() {
return "User cancelled operation";
}
};
// A thing that can be cancelled by something else.
class Cancellee {
friend class Canceller; // Give them access to RegisterCanceller()
protected:
Cancellee() : pCancelledFlag(0) {}
// Does nothing if unconnected
void CheckForCancellation() {
if (pCancelledFlag && *pCancelledFlag) throw Cancellation();
}
private:
void RegisterCanceller(volatile bool& cancelledFlag) {
pCancelledFlag = &cancelledFlag;
}
volatile bool* pCancelledFlag;
};
class Op1 : public Cancellee { // (And similarly for Op2 and Op3)
...
// Poll CheckForCancellation() inside main working loop
...
};
MyThread
{
CancelButton cancelButton("CANCEL!");
try {
ClassWithLongOperation Op1(10);
cancelButton.RegisterCancellee(Op1);
Op1.Run(); // Takes several minutes.
ClassWithLongOperation Op2(20);
cancelButton.RegisterCancellee(Op2);
Op2.Run();
SomeOtherClassWithLongOperation Op3;
cancelButton.RegisterCancellee(Op3);
Op3.Run();
} catch (Cancellation& c) {
// Maybe write to a log file
}
// Do some other stuff
}
“双重弹跳”注册允许取消者访问私有标志变量。
最重要的是不使用线程终止函数,除非是非常特殊的情况。为什么?他们不运行析构函数。它们也没有给目标线程任何“清理”的机会。
答案 2 :(得分:1)
不是使用全局变量,而是向ClassWithLongOperation和/或MyThread添加一个方法,类似于设置内部布尔变量的cancelOperation()。然后,适当的类方法需要在适当的时刻检查变量。
答案 3 :(得分:1)
您可以为ClassWithLongOperation实现一个Stop()方法,并让BigFatCancelButton的事件处理程序为当前操作调用此Stop()方法。
答案 4 :(得分:0)
...或者向Thread类添加一个Stop()方法,让工作对象知道它们正在运行的线程。你也可以为工作对象抛出一个Stop()方法。取决于更重要的事情:停止线程或工作对象。