6

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

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。

4

5 回答 5

4

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

于 2012-09-06T12:49:37.933 回答
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
}

“双弹跳”注册允许取消程序访问私有标志变量。

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

于 2012-09-06T13:10:16.323 回答
1

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

于 2012-09-06T12:48:33.573 回答
1

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

于 2012-09-06T12:49:09.447 回答
0

...或者向 Thread 类添加一个 Stop() 方法,并使工作对象知道它们正在运行的线程。您也可以为工作对象添加一个 Stop() 方法。取决于更重要的:停止线程或工作对象。

于 2012-09-06T13:02:07.230 回答