C++ 取消运行长操作的线程

C++ 取消运行长操作的线程,c++,multithreading,oop,design-patterns,C++,Multithreading,Oop,Design Patterns,我正在努力解决我遇到的设计难题 ClassWithLongOperation { Run() { RecrusiveOperation(); } RecrusiveOperation() { /* RECURSION */ } } MyThread { ClassWithLongOperation Op1(10); Op1.Run(); // Takes several minutes.

我正在努力解决我遇到的设计难题

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上有一个很大的取消按钮,这样用户就可以取消操作了

< >我可以创建全局布尔变量b取消,并检查它是否已设置在RecursiveOperation,但我想成为一个优秀的C++和面向对象编程人员,避免全局变量。特别是如果它们必须分布在多个文件中

那么,我如何(遵循良好的设计)安全地取消MyThread呢?我可以在设置中更改什么以允许此操作


我也在使用
\u beginthreadex
来启动线程,但是如果允许更简单的解决方案,我可以使用boost。

不要使用全局变量,而是向ClassWithLongOperation和/或MyThread添加一个方法,类似于cancelOperation(),它将设置一个内部布尔变量。然后,适当的类方法需要在适当的时刻检查变量。

您可以为ClassWithLongOperation实现Stop()方法,并让BigFatCancelButton的事件处理程序为当前操作调用此Stop()方法。

您的标志不必是整个程序的全局标志,但它需要对类代码可见。创建标志作为私有实例成员,创建公共函数将其更改为false/true。在递归函数中,测试其值以验证任务是否应继续。如果需要,将其值设置为false(当然是通过函数)以停止递归调用,即当用户单击按钮时,您在所需实例中调用函数。这样您就不会违反任何OO原则,因为您有一个私有标志和一个公共成员函数来安全地更改它。

。。。或者向Thread类中添加Stop()方法,使工作对象知道它们正在运行的线程。您还可以为工作对象引入一个Stop()方法。根据更重要的内容:停止线程或工作对象。

使用全局变量实际上并不是世界上最糟糕的事情。不必要的全局变量激增会导致维护噩梦,但实际上,这听起来像是一个快速且易于理解的解决方案。但如果您想要一个干净的OO解决方案,这当然是可能的:

编辑我最初的帖子忽略了一个事实,即您希望能够按顺序运行多个操作,如果其中任何操作被取消,则不会执行其余操作。这意味着将
bool
标志保留在消除器中更有用,而不是在每个可取消操作中单独使用;异常是处理实际控制流的最好方法。我还加强了一些措施(为标志本身添加了
volatile
,使名称更清晰,限制了不必要的访问权限)

“双反弹”注册允许取消器授予对私有标志变量的访问权


最重要的是不要使用线程终止函数,除非是在非常特殊的情况下。为什么?他们不运行析构函数。它们也不会给目标线程任何“清理”的机会。

我有点喜欢这种方法,但如果我正确理解代码,这意味着用户必须多次按“取消”来取消每个长操作。@Fiktik:不,用户只需按一次“取消”。。。你凭什么认为需要多台印刷机?(当然可能是一个bug…)好吧,我理解OP的问题的方式是,他想要一个按钮来中断他的线程应该做的所有工作。但是,对于操作链中的每个操作,您都有
cancelled
成员-这意味着用户按了CANCEL键,当前操作被中断,只是线程继续执行下一个操作。@Fiktik:您说得很对,这就从我身边消失了。我会重新考虑我的设计…这很好+1.不过,我不确定在这里使用
volatile
的原因是什么。为了避免在c++11中使用UB,您通常会将其设置为原子的,在c++03中,我认为这里的
volatile
没有多大区别-你害怕一些优化技术弄乱代码吗?但是我必须让
ClassWithLongOperation
SomeOtherClassWithLongOperation
的实例对GUI可见。我正试图把它们包含在线程中。@Josh:不一定。您可以为调用函数的线程创建函数,以配置实例的标志。然后,在GUI中,调用线程函数。
// 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
}