Est-il possible d’abandonner une tâche comme l’abandon d’un thread (méthode Thread.Abort)?

Nous pourrions abandonner un thread comme celui-ci:

Thread thread = new Thread(SomeMethod); . . . thread.Abort(); 

Mais puis-je abandonner une tâche (dans .Net 4.0) de la même manière que par un mécanisme d’annulation. Je veux tuer la tâche immédiatement.

  1. Vous ne devriez pas utiliser Thread.Abort ()
  2. Les tâches peuvent être annulées mais pas annulées.

La méthode Thread.Abort () est (gravement) obsolète.

Les threads et les tâches doivent coopérer lorsqu’ils sont arrêtés, sinon vous risquez de laisser le système dans un état instable / indéfini.

Si vous devez exécuter un processus et le supprimer de l’extérieur, la seule option sûre consiste à l’exécuter dans un AppDomain distinct.

Les conseils pour ne pas utiliser un avortement de thread sont controversés. Je pense qu’il y a encore de la place mais dans des circonstances exceptionnelles. Cependant, vous devriez toujours essayer de concevoir autour de lui et de le voir en dernier recours.

Exemple;

Vous avez une application de formulaire Windows simple qui se connecte à un service Web synchrone bloquant. Dans lequel il exécute une fonction sur le service Web dans une boucle parallèle.

 CancellationTokenSource cts = new CancellationTokenSource(); ParallelOptions po = new ParallelOptions(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = System.Environment.ProcessorCount; Parallel.ForEach(iListOfItems, po, (item, loopState) => { Thread.Sleep(120000); // pretend web service call }); 

Dites dans cet exemple, l’appel de blocage prend 2 minutes pour se terminer. Maintenant, je règle mon MaxDegreeOfParallelism sur ProcessorCount. iListOfItems contient 1000 éléments à traiter.

L’utilisateur clique sur le bouton de processus et la boucle commence, nous avons 20 threads exécutés sur 1000 éléments de la collection iListOfItems. Chaque itération s’exécute sur son propre thread. Chaque thread utilisera un thread de premier plan lorsqu’il sera créé par Parallel.ForEach. Cela signifie que, indépendamment de l’arrêt de l’application principale, le domaine de l’application sera maintenu actif jusqu’à ce que tous les threads soient terminés.

Cependant, l’utilisateur doit fermer l’application pour une raison quelconque, par exemple, fermer le formulaire. Ces 20 threads continueront à s’exécuter jusqu’à ce que tous les 1000 éléments soient traités. Ce n’est pas idéal dans ce scénario, car l’application ne se fermera pas comme le prévoit l’utilisateur et continuera à s’exécuter en arrière-plan, comme on peut le voir en consultant le gestionnaire de tâches.

Supposons que l’utilisateur essaie de reconstruire l’application à nouveau (VS 2010). Il signale que l’exe est verrouillé. Il devra alors entrer dans le gestionnaire de tâches pour le tuer ou attendre que tous les 1000 éléments soient traités.

Je ne vous reprocherais pas de dire, mais bien sûr! Je devrais annuler ces threads en utilisant l’object CancellationTokenSource et en appelant Cancel … mais il y a quelques problèmes avec .net 4.0. Tout d’abord, cela n’aboutira jamais à un abandon de thread qui offrirait une exception d’abandon suivie d’une fin de thread, le domaine d’application devra donc attendre que les threads se terminent normalement, ce qui signifie attendre le dernier appel bloquant, qui serait la toute dernière itération (thread) en cours d’exécution qui appelle po.CancellationToken.ThrowIfCancellationRequested . Dans l’exemple, cela signifierait que le domaine d’application pourrait restr actif jusqu’à 2 minutes, même si le formulaire a été fermé et annulé.

Notez que l’appel de CancelTokenSource ne lance pas d’exception sur les threads de traitement, ce qui aurait pour effet d’interrompre l’appel bloquant similaire à un abandon de thread et d’arrêter l’exécution. Une exception est mise en cache lorsque tous les autres threads (itérations simultanées) finissent et retournent, l’exception est lancée dans le thread initiateur (où la boucle est déclarée).

J’ai choisi de ne pas utiliser l’option Annuler sur un object CancellationTokenSource. Ceci est un gaspillage et viole sans doute l’anti-patten bien connu de contrôler le stream du code par des exceptions.

Au lieu de cela, il est sans doute «préférable» d’implémenter une propriété de thread simple, par exemple Bool stopExecuting. Ensuite, dans la boucle, vérifiez la valeur de stopExecuting et si la valeur est définie sur true par l’influence externe, nous pouvons choisir un autre chemin pour fermer normalement. Comme nous ne devrions pas appeler cancel, cela empêche de vérifier CancellationTokenSource.IsCancellationRequested qui serait autrement une autre option.

Quelque chose comme ce qui suit si la condition est appropriée dans la boucle;

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || stopExecuting) {loopState.Stop (); revenir;}

L’itération va maintenant se terminer de manière “contrôlée” tout en mettant fin à d’autres itérations, mais comme je l’ai dit, cela ne fait pas grand chose pour notre problème de devoir attendre les longs appels et blocages effectués dans chaque itération ( fil de boucle parallèle), car ceux-ci doivent être terminés avant que chaque thread puisse accéder à l’option permettant de vérifier s’il doit s’arrêter.

En résumé, lorsque l’utilisateur ferme le formulaire, les 20 threads seront signalés pour s’arrêter via stopExecuting, mais ils ne s’arrêteront que lorsqu’ils auront fini d’exécuter leur appel de fonction en cours d’exécution.

Nous ne pouvons rien faire concernant le fait que le domaine d’application restra toujours actif et ne sera libéré que lorsque tous les threads de premier plan seront terminés. Et cela signifie qu’il y aura un délai associé à l’attente de la fin des appels bloquants effectués dans la boucle.

Seul un véritable abandon de thread peut interrompre l’appel bloquant, et vous devez limiter le fait de laisser le système dans un état instable / indéfini, le mieux possible dans le gestionnaire d’exceptions du thread interrompu, qui va sans aucun doute. Le programmeur doit décider si cela est approprié, en fonction des descripteurs de ressources qu’ils ont choisi de conserver et de la facilité de les fermer dans le bloc finalement du thread. Vous pouvez vous enregistrer avec un jeton pour terminer en annulation en tant que solution de contournement, par exemple

 CancellationTokenSource cts = new CancellationTokenSource(); ParallelOptions po = new ParallelOptions(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = System.Environment.ProcessorCount; Parallel.ForEach(iListOfItems, po, (item, loopState) => { using (cts.Token.Register(Thread.CurrentThread.Abort)) { Try { Thread.Sleep(120000); // pretend web service call } Catch(ThreadAbortException ex) { // log etc. } Finally { // clean up here } } }); 

mais cela entraînera toujours une exception dans le thread de déclaration.

Tout bien considéré, l’interruption des appels bloquants en utilisant les constructions parallel.loop aurait pu être une méthode sur les options, évitant l’utilisation de parties plus obscures de la bibliothèque. Mais pourquoi il n’y a pas d’option pour annuler et éviter de lancer une exception dans la méthode de déclaration me frappe comme un possible oubli.

Mais puis-je abandonner une tâche (dans .Net 4.0) de la même manière que par un mécanisme d’annulation. Je veux tuer la tâche immédiatement .

D’autres répondeurs vous ont dit de ne pas le faire. Mais oui, vous pouvez le faire. Vous pouvez fournir Thread.Abort() tant que délégué à appeler par le mécanisme d’annulation de la tâche. Voici comment vous pouvez configurer ceci:

 class HardAborter { public bool WasAborted { get; private set; } private CancellationTokenSource Canceller { get; set; } private Task Worker { get; set; } public void Start(Func DoFunc) { WasAborted = false; // start a task with a means to do a hard abort (unsafe!) Canceller = new CancellationTokenSource(); Worker = Task.Factory.StartNew(() => { try { // specify this thread's Abort() as the cancel delegate using (Canceller.Token.Register(Thread.CurrentThread.Abort)) { return DoFunc(); } } catch (ThreadAbortException) { WasAborted = true; return false; } }, Canceller.Token); } public void Abort() { Canceller.Cancel(); } } 

avertissement : ne le faites pas.

Voici un exemple de quoi ne pas faire:

  var doNotDoThis = new HardAborter(); // start a thread writing to the console doNotDoThis.Start(() => { while (true) { Thread.Sleep(100); Console.Write("."); } return null; }); // wait a second to see some output and show the WasAborted value as false Thread.Sleep(1000); Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted); // wait another second, abort, and print the time Thread.Sleep(1000); doNotDoThis.Abort(); Console.WriteLine("Abort sortingggered at " + DateTime.Now); // wait until the abort finishes and print the time while (!doNotDoThis.WasAborted) { Thread.CurrentThread.Join(0); } Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted + " at " + DateTime.Now); Console.ReadKey(); 

sortie de code exemple

Bien qu’il soit possible d’interrompre un thread, en pratique, c’est presque toujours une très mauvaise idée de le faire. Aborthing d’un thread signifie que le thread n’a pas de chance de se nettoyer après lui-même, laissant les ressources non supprimées et les choses dans des états inconnus.

En pratique, si vous abandonnez un thread, vous ne devez le faire que lors de la destruction du processus. Malheureusement, trop de gens pensent que ThreadAbort est un moyen viable d’arrêter quelque chose et de continuer, ce n’est pas le cas.

Comme les tâches s’exécutent en tant que threads, vous pouvez appeler ThreadAbort, mais comme pour les threads génériques, vous ne voulez presque jamais le faire, sauf en dernier recours.

 using System; using System.Threading; using System.Threading.Tasks; ... var cts = new CancellationTokenSource(); var task = Task.Run(() => { while (true) { } }); Parallel.Invoke(() => { task.Wait(cts.Token); }, () => { Thread.Sleep(1000); cts.Cancel(); }); 

Ceci est un extrait de code simple qui annule une tâche sans fin avec CancellationTokenSource .

Si vous avez un constructeur Task, nous pouvons extraire Thread de la tâche et appeler thread.abort.

 Thread th = null; Task.Factory.StartNew(() => { th = Thread.CurrentThread; while (true) { Console.WriteLine(DateTime.UtcNow); } }); Thread.Sleep(2000); th.Abort(); Console.ReadKey();