47 votes

Comment faire une méthode annulable sans il de plus moche?

Je suis actuellement dans le processus de modernisation de longue exécution de méthodes pour être annulable. Je suis de la planification sur l'utilisation du Système.Le filetage.Les tâches.CancellationToken à la mettre en œuvre.

Nos méthodes généralement effectuer un peu longue étapes de l'envoi des commandes et puis d'attendre pour le matériel la plupart du temps), par exemple

void Run()
{
    Step1();
    Step2();    
    Step3();
}

Mon premier (peut-être stupide) de la pensée sur l'annulation à les transformer en

bool Run(CancellationToken cancellationToken)
{
    Step1(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    Step2(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;    

    Step3(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    return true;
}

qui, franchement, semble horrible. Ce "modèle" continuera à l'intérieur de l'unique étapes, (et ils sont nécessairement assez longuets déjà). Ce serait faire du Fil.Abort() l'allure plutôt sexy, même si je sais que c'est pas recommandé.

Est-il plus propre patron pour réaliser ce qui ne se cache pas loin de la logique de l'application en dessous de beaucoup de code réutilisable?

Modifier

Comme un exemple de la nature des mesures, l' Run méthode pourrait lire

void Run()
{
    GiantRobotor.MoveToBase();
    Oven.ThrowBaguetteTowardsBase();    
    GiantRobotor.CatchBaguette();
    // ...
}

Nous sommes le contrôle de différentes unités matérielles qui doivent être synchronisés à travailler ensemble.

33voto

Matten Points 9394

Si les étapes sont en quelque sorte independend concernant les flux de données dans la méthode, mais ne peut pas être exécuté en parallèle, la matière, l'approche suivante peut être mieux lisible:

void Run()
{
    // list of actions, defines the order of execution
    var actions = new List<Action<CancellationToken>>() {
       ct => Step1(ct),
       ct => Step2(ct),
       ct => Step3(ct) 
    };

    // execute actions and check for cancellation token
    foreach(var action in actions)
    {
        action(cancellationToken);

        if (cancellationToken.IsCancellationRequested)
            return false;
    }

    return true;
}

Si les mesures n'ont pas besoin de l'annulation jeton parce que vous pouvez les répartir dans les petites unités, vous pouvez même écrire une liste plus petite définition:

var actions = new List<Action>() {
    Step1, Step2, Step3
};

23voto

Nickolai Nielsen Points 710

ce sujet de la suite?

var t = Task.Factory.StartNew(() => Step1(cancellationToken), cancellationToken)
   .ContinueWith(task => Step2(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)
   .ContinueWith(task => Step3(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);

9voto

Mike Strobel Points 6585

J'avoue il n'est pas assez, mais l'orientation est de faire ce que vous avez fait:

if (cancellationToken.IsCancellationRequested) { /* Stop */ }

...ou légèrement plus courte:

cancellationToken.ThrowIfCancellationRequested()

En général, si vous pouvez passer le jeton d'annulation pour chacune des étapes, vous pouvez étendre la vérifie de manière à ne pas saturer le code. Vous pouvez également choisir de ne pas vérifier pour l'annulation de la permanence; si les opérations que vous effectuez sont idempotents et ne sont pas gourmandes en ressources, vous n'avez pas nécessairement besoin de vérifier pour annulation à chaque étape. Le moment le plus important est de vérifier avant de retourner un résultat.

Si vous êtes de passage le pion à toutes les étapes de votre, vous pourriez faire quelque chose comme ceci:

public static CancellationToken VerifyNotCancelled(this CancellationToken t) {
    t.ThrowIfCancellationRequested();
    return t;
}

...

Step1(token.VerifyNotCancelled());
Step2(token.VerifyNotCancelled());
Step3(token.VerifyNotCancelled());

7voto

Jim Mischel Points 68586

Lorsque j'ai eu à faire quelque chose comme ça, j'ai créé un délégué à le faire:

bool Run(CancellationToken cancellationToken)
{
    var DoIt = new Func<Action<CancellationToken>,bool>((f) =>
    {
        f(cancellationToken);
        return cancellationToken.IsCancellationRequested;
    });

    if (!DoIt(Step1)) return false;
    if (!DoIt(Step2)) return false;
    if (!DoIt(Step3)) return false;

    return true;
}

Ou, si il n'y a jamais de code entre les étapes, vous pouvez écrire:

return DoIt(Step1) && DoIt(Step2) && DoIt(Step3);

4voto

Niko Drašković Points 3066

Version courte:

Utiliser lock() pour synchroniser Thread.Abort() d'appel avec des sections critiques.


Laissez-moi vous expliquer version:

Généralement, lors de l'abandon du thread, vous devez tenir compte de deux types de code:

  • Long de l'exécution de code vous n'avez pas vraiment s'il se termine
  • Le Code qui doit absolument terme, c'est sûr

Premier type est le type nous ne nous soucions pas si l'utilisateur a demandé l'abandonner. Nous avons peut-être compter jusqu'à une centaine de milliards de dollars et il ne se soucie pas plus?

Si nous avions utilisation des sentinelles comme l' CancellationToken, nous aurions à peine de les tester à chaque itération de l'importance du code, nous?

for(long i = 0; i < bajillion; i++){
    if(cancellationToken.IsCancellationRequested)
        return false;
    counter++;
}

Si laid. Donc, pour ces cas, Thread.Abort() est une aubaine.

Malheureusement, comme certains le disent, vous ne pouvez pas utiliser Thread.Abort() en raison de la atomique code qui n'a absolument! Le code a déjà soustrait de l'argent de votre compte, maintenant, c'est de compléter la transaction et le transfert de l'argent du compte cible. Personne n'aime l'argent en train de disparaître.

Heureusement, nous avons d'exclusion mutuelle pour nous aider avec ce genre de chose. Et C# rend assez:

//unimportant long task code
lock(_lock)
{
    //atomic task code
}

Et d'ailleurs

lock(_lock) //same lock
{
    _thatThread.Abort();
}

Le nombre de verrous sera toujours <= nombre de sentinelles, parce que vous seriez désireux sentinelles sur l'importance de code de trop (pour faire avorter le plus rapide). Cela rend le code légèrement plus jolie que la sentinelle version, mais il fait aussi de l'abandon de mieux, parce qu'il n'a pas à attendre pour des choses sans importance.


Également à noter est que l' ThreadAbortException pourrait être posée n'importe où, même dans finally blocs. Cette imprévisibilité Thread.Abort() si controversée.

Avec des serrures vous feriez pour éviter cela, juste le verrouillage de l'ensemble de l' try-catch-finally bloc. Le nettoyage en finally est essentiel, l'ensemble du bloc peut être verrouillé. try des blocs sont généralement fait pour être aussi courte que possible, une ligne de préférence, de sorte que nous n'avons pas de verrouillage tout code inutile.

Cela rend Thread.Abort(), comme vous le dites, un peu moins mal. Vous ne voulez pas vous appeler sur le thread de l'INTERFACE utilisateur, même si, comme vous êtes maintenant en la verrouillant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X