取消运行长操作的线程

时间:2012-09-06 12:45:32

标签: c++ multithreading oop design-patterns

我正在努力解决我的设计困境。

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。

5 个答案:

答案 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()方法。取决于更重要的事情:停止线程或工作对象。