6
votes

Annulation d'un fil d'exécution d'une opération longue

J'essaie de résoudre un problème de conception que j'ai.

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
}


0 commentaires

5 Réponses :


1
votes

Au lieu d'utiliser une variable globale, ajoutez une méthode à la classe et / ou à mythread, quelque chose comme la cancanal () qui définira une variable booléenne interne. Les méthodes de classe appropriées auraient alors besoin de vérifier la variable à des moments appropriés.


0 commentaires

1
votes

Vous pouvez implémenter une méthode STOP () pour votre classeWithLongoperation et avoir le gestionnaire d'événements pour BigFatCancelbutton d'appeler cette méthode d'arrêt () pour l'opération en cours.


0 commentaires

4
votes

Votre drapeau n'a pas besoin d'être global pour votre programme entier, mais il doit être visible pour votre code de classe. Créez le drapeau pour être membre d'instance privée et une fonction publique pour la modifier à FALSE / TRUE. Dans votre fonction récursive, testez sa valeur pour vérifier si la tâche devrait continuer. Lorsque vous souhaitez, définissez sa valeur sur FALSE (via la fonction bien sûr) pour arrêter les appels récursifs, c'est-à-dire lorsque l'utilisateur clique sur le bouton, vous appelez la fonction dans l'instance souhaitée. De cette façon, vous ne briserez aucun principe OO, car vous avez un drapeau privé et une fonction de membre public pour le changer en toute sécurité.


2 commentaires

Mais alors je devrais faire les instances de de classewithlongopération et quelque ouverteclasswithlongopération visible à l'interface graphique. J'essayais de les contenir dans le fil.


@Josh: Pas nécessairement. Vous pouvez créer une fonction pour le fil qui appelle les fonctions pour configurer les indicateurs des instances. Ensuite, dans votre interface graphique, vous appelez la fonction de thread.



0
votes

... ou ajoutez une méthode d'arrêt () à la classe de thread et faites connaître les objets de travail des threads qu'ils utilisent. Vous pouvez aussi bien lancer une méthode d'arrêt () pour les objets de travail. En fonction de ce qui est plus important: arrêtez le fil ou l'objet de travail.


0 commentaires

3
votes

L'utilisation d'une variable globale n'est en fait pas la pire chose au monde. Avoir une prolifération de variables globales inutiles conduit à des cauchemars de maintenance, mais cela ressemble à une solution rapide et facile à comprendre ici. Mais si vous voulez une solution de OO propre, ceci est certainement possible:

EDIT STROND> Mon message d'origine négligé le fait que vous souhaitez pouvoir exécuter plusieurs opérations en séquence et si l'un d'entre eux est annulé, aucune des opérations restantes n'est effectuée. Cela signifie qu'il est plus utile de conserver le drapeau bool code> à l'intérieur de l'annulation, au lieu de séparément dans chaque opération annulable; Et les exceptions sont le moyen le plus agréable de gérer le flux de contrôle réel. J'ai aussi resserré quelques objets (ajouté volatile code> pour le drapeau lui-même, fait les noms de noms, des droits d'accès inutiles restreints). P>

// 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
}


8 commentaires

Je suis un peu comme l'approche, mais si je comprends le code à droite, cela signifie que l'utilisateur doit appuyer plusieurs fois sur Annuler pour annuler chaque longopération.


@Fiktik: Non, l'utilisateur n'a besoin que d'appuyer sur "Annuler" une fois ... Qu'est-ce qui vous fait penser que plusieurs presses seraient nécessaires? (Pourrait être un bug bien sûr ...)


Eh bien, la façon dont je comprends la question de l'OP est qu'il veut avoir un bouton qui perturbe tout le travail que son fil est censé faire. Mais vous avez Annulé membre pour chaque opération de la chaîne d'opérations - cela signifie que l'utilisateur appuie sur Annuler et que l'opération actuelle est perturbée, uniquement pour le fil de continuer avec l'opération suivante.


@Fiktik: Tu as tout à fait raison, ça m'a passé juste après moi. Je repenserai mon design ...


@Fiktik: Faites-moi savoir ce que vous pensez du nouveau design.


C'est sympa. +1. Je ne sais pas quelle est la raison de l'utilisation de volatile ici cependant. Pour éviter l'UB en C ++ 11, vous le feriez normalement atomic , dans c ++ 03 le volatile ici ne fait pas beaucoup de différence que je pense - sont Vous avez peur de certaines techniques d'optimisation qui gâchent le code?


@Fiktik: Je ne suis pas à jour avec le C ++ 11 Atomic Sémantique J'ai peur - vous pourriez peut-être modifier les équivalents C ++ 11 appropriés en tant que commentaires (ou laissez mon C ++ 03 comme des commentaires)? Certainement en C ++ 03, vous devez étiqueter une variable avec volatile si l'optimisateur du compilateur ne doit pas supposer qu'il connaît toutes les modifications apportées à cette variable et doit recharger de la mémoire à chaque accès ( étuis typiques étant lorsqu'il est mis à jour à partir d'autres threads ou gestionnaires de signaux).


Vous avez raison sur le volatile . Je me suis confus pendant un moment. Les changements d'utilisation des atomiques C ++ 11 doivent être assez simples. Je vais le modifier (bien que je ne sois pas tout à fait confiant sur la sémantique, de sorte que toute suggestion de suggestions).