简体   繁体   English

取消运行长操作的线程

[英]Cancelling a thread running a long operation

I'm trying to work out a design predicament I have. 我正在努力解决我的设计困境。

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
}

The GUI starts MyThread , which runs for a good 5-6 minutes. GUI启动MyThread ,运行时间为5-6分钟。 I want to be able to have a big fat Cancel button on my GUI, so the user can cancel the operation. 我希望能够在我的GUI上有一个大胖的取消按钮,因此用户可以取消操作。

I could create a global boolean variable bCancelled , and check if its been set in RecursiveOperation , but I want to be a good C++ & OO programmer and avoid global variables. 我可以创建一个全局布尔变量bCancelled ,并检查它是否已在RecursiveOperation中设置,但我想成为一个优秀的C ++和OO程序员并避免全局变量。 Especially if they would have to spread across multiple files. 特别是如果它们必须分布在多个文件中。

So how would I (following good design) safely cancel MyThread ? 那么我(按照良好的设计)如何安全地取消MyThread What could I change in my setup to allow this? 我可以在设置中更改哪些内容以允许此操作?

I'm also using _beginthreadex to start the thread, but I could use boost if it would allow for an easier solution. 我也使用_beginthreadex来启动线程,但如果能够提供更简单的解决方案,我可以使用boost。

Your flag not need to be global to your entire program, but it needs to be visible to your class code. 您的标志不需要对整个程序是全局的,但它需要在您的类代码中可见。 Create the flag to be a private instance member and a public function to change it to false/true. 将标志创建为私有实例成员,将公共函数创建为false / true。 In your recursive function, test its value to verify if the task should continue. 在递归函数中,测试其值以验证任务是否应该继续。 When you want, set its value to false (through the function of course) to stop the recursive calls, ie, when the user clicks the button you call the function in the desired instance. 如果需要,可以将其值设置为false(通过当然功能)来停止递归调用,即,当用户单击按钮时,您可以在所需的实例中调用该函数。 This way you will not break any OO principle, since you have a private flag and a public member function to safely change it. 这样你就不会破坏任何OO原则,因为你有一个私有标志和一个公共成员函数来安全地改变它。

Using a global variable is actually not the worst thing in the world. 使用全局变量实际上并不是世界上最糟糕的事情。 Having a proliferation of unnecessary global variables leads to maintenance nightmares, but it actually sounds like a quick and easy-to-understand solution here. 拥有不必要的全局变量会导致维护噩梦,但实际上这听起来像是一个快速且易于理解的解决方案。 But if you want a clean OO solution, this is certainly possible: 但是如果你想要一个干净的OO解决方案,这肯定是可能的:

EDIT My original post overlooked the fact that you want to be able to run several operations in sequence, and if any of them is cancelled, none of the remaining operations are performed. 编辑我的原始帖子忽略了一个事实,即您希望能够按顺序运行多个操作,如果其中任何一个被取消,则不执行任何剩余的操作。 This means it's more useful to keep the bool flag inside the canceller, instead of separately in each cancellable operation; 这意味着将bool标志保留在取消器内更有用,而不是在每个可取消操作中单独保存; and exceptions are the nicest way to handle the actual control flow. 和异常是处理实际控制流的最好方法。 I've also tightened up a few things (added volatile for the flag itself, made names clearer, restricted unnecessary access rights). 我还收紧了一些东西(为标志本身添加了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
}

The "double bouncing" registration allows the canceller to give access to a private flag variable. “双弹跳”注册允许取消者访问私有标志变量。

The most important thing is to not use thread termination functions, except in very specialised cases. 最重要的是使用线程终止函数,除非在非常特殊的情况下。 Why? 为什么? They don't run destructors. 他们不运行析构函数。 Nor do they give the target thread any chance to "clean up". 它们也没有给目标线程任何“清理”的机会。

Instead of using a global variable, add a method to ClassWithLongOperation and/or MyThread, something like cancelOperation() that will set an internal boolean variable. 不是使用全局变量,而是向ClassWithLongOperation和/或MyThread添加一个方法,类似于设置内部布尔变量的cancelOperation()。 The appropriate class methods would then need to check the variable at appropriate moments. 然后,适当的类方法需要在适当的时刻检查变量。

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

... Or add a Stop() method to the Thread class and make the work objects be aware of the threads they're running in. You may as well throw in a Stop() method for the work objects. ...或者向Thread类添加一个Stop()方法,让工作对象知道它们正在运行的线程。你也可以为工作对象抛出一个Stop()方法。 Depending on what's more important: Stop the thread or the work object. 取决于更重要的事情:停止线程或工作对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM