È ansible abortire un’attività come abortire una discussione (metodo Thread.Abort)?

Potremmo abortire un thread come questo:

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

Ma posso abortire un compito (in .Net 4.0) nello stesso modo non con il meccanismo di cancellazione. Voglio uccidere immediatamente il compito.

  1. Non dovresti usare Thread.Abort ()
  2. Le attività possono essere annullate ma non annullate.

Il metodo Thread.Abort () è (severamente) deprecato.

Entrambi i thread e le attività devono cooperare quando vengono arrestati, altrimenti si corre il rischio di lasciare il sistema in uno stato instabile / indefinito.

Se è necessario eseguire un processo e ucciderlo dall’esterno, l’unica opzione sicura è eseguirlo in un AppDomain separato.

La guida per non utilizzare l’interruzione di thread è controversa. Penso che ci sia ancora un posto per questo, ma in circostanze eccezionali. Tuttavia, dovresti sempre cercare di disegnare attorno ad esso e vederlo come ultima risorsa.

Esempio;

Si dispone di una semplice applicazione Windows form che si connette a un servizio Web sincrono di blocco. All’interno del quale esegue una funzione sul servizio web all’interno di un loop parallelo.

 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 }); 

Supponiamo che in questo esempio la chiamata di blocco richieda 2 minuti per essere completata. Ora ho impostato il mio MaxDegreeOfParallelism per dire ProcessorCount. iListOfItems contiene al suo interno 1000 articoli da elaborare.

L’utente fa clic sul pulsante di elaborazione e il ciclo inizia, abbiamo “fino a” 20 thread in esecuzione contro 1000 elementi nella raccolta iListOfItems. Ogni iterazione viene eseguita sul proprio thread. Ogni thread utilizzerà un thread in primo piano quando viene creato da Parallel.ForEach. Ciò significa che, indipendentemente dallo spegnimento principale dell’applicazione, il dominio dell’app verrà mantenuto attivo fino al termine di tutti i thread.

Tuttavia, l’utente deve chiudere l’applicazione per qualche motivo, ad esempio chiudi il modulo. Questi 20 thread continueranno ad essere eseguiti fino a quando tutti e 1000 gli articoli saranno processati. Questo non è l’ideale in questo scenario, in quanto l’applicazione non uscirà come l’utente si aspetta e continuerà a correre dietro le quinte, come si può vedere dando un’occhiata al task manager.

Supponiamo che l’utente tenti di ribuild nuovamente l’app (VS 2010), segnala che l’exe è bloccato, quindi dovrebbero passare al task manager per ucciderlo o attendere fino a quando non vengono elaborati tutti i 1000 elementi.

Non ti biasimo per averlo detto, ma ovviamente! Dovrei cancellare questi thread usando l’object CancellationTokenSource e chiamando Cancel … ma ci sono alcuni problemi con questo da .net 4.0. In primo luogo, non si verificherà mai un’interruzione del thread che offrirebbe un’eccezione di interruzione seguita dalla terminazione del thread, quindi il dominio dell’app dovrà attendere che i thread finiscano normalmente, e questo significa attendere l’ultima chiamata bloccante, quale sarebbe l’ultima iterazione (thread) che alla fine arriva a chiamare po.CancellationToken.ThrowIfCancellationRequested . Nell’esempio, ciò significherebbe che il dominio dell’app potrebbe rimanere attivo fino a 2 minuti, anche se il modulo è stato chiuso e annullato.

Tieni presente che la chiamata a Cancel su CancellationTokenSource non genera un’eccezione sul thread o sui thread di elaborazione, che agirebbe in effetti per interrompere la chiamata di blocco simile a una interruzione di thread e interrompere l’esecuzione. Un’eccezione è memorizzata nella cache pronta per quando tutti gli altri thread (iterazioni concorrenti) alla fine finiscono e ritornano, l’eccezione viene generata nel thread di avvio (dove viene dichiarato il ciclo).

Ho scelto di non utilizzare l’opzione Annulla su un object CancellationTokenSource. Ciò è dispendioso e probabilmente viola il noto anti-patten di controllo del stream del codice da parte di Eccezioni.

Invece, è indubbiamente “meglio” implementare una semplice proprietà thread-safe, ad esempio Bool stopExecuting. Quindi all’interno del ciclo, controlla il valore di stopExecuting e se il valore è impostato su true dall’influenza esterna, possiamo prendere un percorso alternativo per chiudere con grazia. Dal momento che non dovremmo chiamare cancel, questo preclude il controllo di CancellationTokenSource.IsCancellationRequested che altrimenti sarebbe un’altra opzione.

Qualcosa come il seguente se la condizione sarebbe appropriata all’interno del ciclo;

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

L’iterazione ora uscirà in un modo “controllato” e terminerà ulteriori iterazioni, ma come ho detto, questo non fa molto per il nostro problema di dover attendere le chiamate di lunga durata e di blocco che vengono fatte all’interno di ogni iterazione ( thread in parallelo), poiché questi devono essere completati prima che ogni thread possa ottenere l’opzione di verificare se deve fermarsi.

In sintesi, quando l’utente chiude il modulo, i 20 thread verranno segnalati per fermarsi tramite stopExecuting, ma si interromperanno solo quando avranno terminato di eseguire la loro chiamata di funzione di lunga durata.

Non possiamo fare nulla per il fatto che il dominio dell’applicazione rimarrà sempre attivo e verrà rilasciato solo quando tutti i thread in primo piano saranno completati. E questo significa che ci sarà un ritardo associato all’attesa di eventuali chiamate di blocco effettuate all’interno del ciclo da completare.

Solo una vera interruzione di thread può interrompere la chiamata di blocco e si deve ridurre il lasciare il sistema in uno stato instabile / indefinito il meglio che si può fare nel gestore di eccezioni del thread interrotto, che passa senza problemi. Se ciò è appropriato è una questione che il programmatore deve decidere, in base a quali handle di risorse hanno scelto di mantenere e quanto è facile chiuderli nel blocco finale di un thread. Potresti registrarti con un token per terminare su cancel come semi-soluzione, ad es

 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 } } }); 

ma questo provocherà comunque un’eccezione nel thread dichiarante.

Tutto sumto, interrompere le chiamate di blocco usando i costrutti paralleli.loop avrebbe potuto essere un metodo sulle opzioni, evitando l’uso di parti più oscure della libreria. Ma perché non vi è alcuna possibilità di annullare ed evitare di lanciare un’eccezione nel metodo di dichiarazione mi sembra una ansible svista.

Ma posso abortire un compito (in .Net 4.0) nello stesso modo non con il meccanismo di cancellazione. Voglio uccidere immediatamente il compito .

Altri rispondenti ti hanno detto di non farlo. Ma sì, puoi farlo. È ansible fornire Thread.Abort() come delegato da chiamare dal meccanismo di annullamento Thread.Abort() . Ecco come puoi configurare questo:

 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(); } } 

disclaimer : non farlo.

Ecco un esempio di cosa non fare:

  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 triggered 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(); 

uscita dal codice di esempio

Mentre è ansible interrompere un thread, in pratica è quasi sempre una pessima idea farlo. L’immissione di un thread significa che al thread non viene data la possibilità di ripulire se stesso, lasciando le risorse non eliminate e le cose in stati sconosciuti.

In pratica, se si interrompe un thread, è necessario farlo solo in concomitanza con l’eliminazione del processo. Tristemente, troppe persone pensano che ThreadAbort sia un modo praticabile per fermare qualcosa e continuare, non lo è.

Poiché le attività vengono eseguite come thread, è ansible chiamare ThreadAbort su di essi, ma come con i thread generici non si desidera quasi mai farlo, tranne che come ultima risorsa.

 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(); }); 

Questo è un semplice frammento per interrompere un’operazione senza fine con CancellationTokenSource .

Se hai un costruttore di Task, allora possiamo estrarre il Thread dall’attività e invocare thread.abort.

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