2011-01-24 23 views
115

In una discussione, creo un po 'di System.Threading.Task e inizio ogni attività.Come annullare/annullare attività TPL?

Quando faccio un .Abort() per interrompere il thread, le attività non vengono annullate.

Come posso trasmettere lo .Abort() alle mie attività?

risposta

176

Non è possibile. Le attività utilizzano thread in background dal pool di thread. Anche l'annullamento di thread usando il metodo Abort non è raccomandato. Puoi dare uno sguardo allo following blog post che spiega un modo corretto di cancellare le attività usando i token di cancellazione. Ecco un esempio:

class Program 
{ 
    static void Main() 
    { 
     var ts = new CancellationTokenSource(); 
     CancellationToken ct = ts.Token; 
     Task.Factory.StartNew(() => 
     { 
      while (true) 
      { 
       // do some heavy work here 
       Thread.Sleep(100); 
       if (ct.IsCancellationRequested) 
       { 
        // another thread decided to cancel 
        Console.WriteLine("task canceled"); 
        break; 
       } 
      } 
     }, ct); 

     // Simulate waiting 3s for the task to complete 
     Thread.Sleep(3000); 

     // Can't wait anymore => cancel this task 
     ts.Cancel(); 
     Console.ReadLine(); 
    } 
} 
+4

bella spiegazione. Ho una domanda, come funziona quando non abbiamo un metodo anonimo in Task.Factory.StartNew? come Task.Factory.StartNew (() => ProcessMyMethod(), cancellationToken) –

+1

Perché non è possibile? Ecco un [esempio] (http://stackoverflow.com/a/19311606/1845402). – starteleport

+39

cosa succede se c'è una chiamata bloccante che non ritorna all'interno dell'attività in esecuzione? – mehmet6parmak

2

L'attività è in esecuzione sul ThreadPool (almeno, se si utilizza la fabbrica predefinita), quindi interrompere la discussione non può influire sulle attività. Per le attività di interruzione, vedere Task Cancellation su msdn.

2

Le attività hanno il supporto di prima classe per la cancellazione via cancellation tokens. Crea i tuoi compiti con i token di cancellazione e annulla le attività tramite questi esplicitamente.

3

È possibile utilizzare un CancellationToken per controllare se l'attività viene annullata. Stai parlando di interromperlo prima che sia iniziato ("non importa, l'ho già fatto"), o in realtà lo interrompo al centro? Se il primo, il CancellationToken può essere utile; in quest'ultimo caso, probabilmente sarà necessario implementare il proprio meccanismo di "salvataggio" e verificare nei punti appropriati nell'esecuzione dell'attività se si dovrebbe fallire velocemente (è comunque possibile utilizzare l'annullamento per aiutare, ma è un po 'più manuale).

MSDN contiene una voce riguardante Tasks cancellazione: http://msdn.microsoft.com/en-us/library/dd997396.aspx

7

Non si dovrebbe cercare di farlo direttamente. Progetta le tue attività per lavorare con un CancellationToken e annullale in questo modo.

Inoltre, consiglierei di cambiare il thread principale in modo che funzioni anche con un CancellationToken. Chiamare Thread.Abort() è una cattiva idea - può portare a vari problemi che sono molto difficili da diagnosticare. Invece, quel thread può usare lo stesso Cancellation utilizzato dalle tue attività - e lo stesso CancellationTokenSource può essere utilizzato per attivare la cancellazione di tutte le delle tue attività e il tuo thread principale.

Questo porterà a un design molto più semplice e più sicuro.

15

Questo genere di cose è uno dei motivi logistici per cui lo Abort è deprecato. Innanzitutto, non utilizzare Thread.Abort() per annullare o interrompere un thread se possibile.Abort() deve essere utilizzato solo per terminare forzatamente un thread che non risponde a richieste più pacifiche di interruzione in modo tempestivo.

Detto questo, è necessario fornire un indicatore di annullamento condiviso che un thread imposta e attende mentre l'altro thread periodicamente controlla ed esce con garbo. .NET 4 include una struttura progettata appositamente per questo scopo, CancellationToken.

22

Interruzione di un compito è facilmente possibile se si cattura il filo in cui l'attività è in esecuzione in Ecco un codice di esempio per dimostrare questo:.

void Main() 
{ 
    Thread thread = null; 

    Task t = Task.Run(() => 
    { 
     //Capture the thread 
     thread = Thread.CurrentThread; 

     //Simulate work (usually from 3rd party code) 
     Thread.Sleep(1000); 

     //If you comment out thread.Abort(), then this will be displayed 
     Console.WriteLine("Task finished!"); 
    }); 

    //This is needed in the example to avoid thread being still NULL 
    Thread.Sleep(10); 

    //Cancel the task by aborting the thread 
    thread.Abort(); 
} 

ho usato Task.Esegui() per mostrare il caso d'uso più comune per questo - usando il comfort di Tasks con il vecchio codice a thread singolo, che non usa la classe CancellationTokenSource per determinare se dovrebbe essere cancellato o meno.

+2

Grazie per questa idea. Utilizzato questo approccio per implementare un timeout per un codice esterno, che non ha il supporto 'CancellationToken' ... – ChrFin

+4

AFAIK thread.abort ti lascerà sconosciuto sullo stack, potrebbe non essere valido.Non l'ho mai provato, ma immagino di iniziare una discussione in un dominio app separato che thread.abort sarà salvato! Inoltre, un intero thread è sprecato nella tua soluzione solo per abortire un compito. Non dovresti usare le attività ma i thread in primo luogo. (downvote) –

+0

Come ho scritto - questa soluzione è l'ultima risorsa che potrebbe essere considerata in determinate circostanze. Ovviamente dovrebbero essere prese in considerazione le soluzioni "CancellationToken" o anche le più semplici che sono esenti da corsa. Il codice sopra illustra solo il metodo, non l'area di utilizzo. –

19

Come this post suggerisce, questo può essere fatto nel modo seguente:

int Foo(CancellationToken token) 
{ 
    Thread t = Thread.CurrentThread; 
    using (token.Register(t.Abort)) 
    { 
     // compute-bound work here 
    } 
} 

Anche se funziona, non è raccomandato l'uso di tale approccio. Se riesci a controllare il codice che viene eseguito nell'attività, è meglio procedere con la corretta gestione della cancellazione.

+5

+1 per dare un approccio diverso mentre si dichiarano i suoi fallback. Non sapevo che si potesse fare :) – Joel

+0

Grazie per la soluzione! Possiamo semplicemente passare il token al metodo e cancellare tokensource, invece di ottenere in qualche modo l'istanza del thread dal metodo e interrompere direttamente questa istanza. – Sam

4

Uso un approccio misto per annullare un'attività.

  • In primo luogo, sto cercando di annullarlo cortesemente usando il Cancellation.
  • Se è ancora in esecuzione (ad esempio a causa di un errore dello sviluppatore), quindi comportarsi male e ucciderlo utilizzando un metodo Abort vecchio stile.

Checkout un esempio qui sotto:

private CancellationTokenSource taskToken; 
private AutoResetEvent awaitReplyOnRequestEvent = new AutoResetEvent(false); 

void Main() 
{ 
    // Start a task which is doing nothing but sleeps 1s 
    LaunchTaskAsync(); 
    Thread.Sleep(100); 
    // Stop the task 
    StopTask(); 
} 

/// <summary> 
///  Launch task in a new thread 
/// </summary> 
void LaunchTaskAsync() 
{ 
    taskToken = new CancellationTokenSource(); 
    Task.Factory.StartNew(() => 
     { 
      try 
      { //Capture the thread 
       runningTaskThread = Thread.CurrentThread; 
       // Run the task 
       if (taskToken.IsCancellationRequested || !awaitReplyOnRequestEvent.WaitOne(10000)) 
        return; 
       Console.WriteLine("Task finished!"); 
      } 
      catch (Exception exc) 
      { 
       // Handle exception 
      } 
     }, taskToken.Token); 
} 

/// <summary> 
///  Stop running task 
/// </summary> 
void StopTask() 
{ 
    // Attempt to cancel the task politely 
    if (taskToken != null) 
    { 
     if (taskToken.IsCancellationRequested) 
      return; 
     else 
      taskToken.Cancel(); 
    } 

    // Notify a waiting thread that an event has occurred 
    if (awaitReplyOnRequestEvent != null) 
     awaitReplyOnRequestEvent.Set(); 

    // If 1 sec later the task is still running, kill it cruelly 
    if (runningTaskThread != null) 
    { 
     try 
     { 
      runningTaskThread.Join(TimeSpan.FromSeconds(1)); 
     } 
     catch (Exception ex) 
     { 
      runningTaskThread.Abort(); 
     } 
    } 
} 
7

Per rispondere alla domanda di Prerak K su come utilizzare CancellationTokens quando non si utilizza un metodo anonimo in Task.Factory.StartNew(), si passa il CancellationToken come parametro nel metodo che stai iniziando con StartNew(), come mostrato nell'esempio MSDN here.

ad es.

var tokenSource = new CancellationTokenSource(); 
var token = tokenSource.Token; 

Task.Factory.StartNew(() => DoSomeWork(1, token), token); 

static void DoSomeWork(int taskNum, CancellationToken ct) 
{ 
    // Do work here, checking and acting on ct.IsCancellationRequested where applicable, 

} 
0

Ho provato CancellationTokenSource ma non posso farlo. E l'ho fatto a modo mio. E funziona.

namespace Blokick.Provider 
{ 
    public class SignalRConnectProvider 
    { 
     public SignalRConnectProvider() 
     { 
     } 

     public bool IsStopRequested { get; set; } = false; //1-)This is important and default `false`. 

     public async Task<string> ConnectTab() 
     { 
      string messageText = ""; 
      for (int count = 1; count < 20; count++) 
      { 
       if (count == 1) 
       { 
       //Do stuff. 
       } 

       try 
       { 
       //Do stuff. 
       } 
       catch (Exception ex) 
       { 
       //Do stuff. 
       } 
       if (IsStopRequested) //3-)This is important. The control of the task stopping request. Must be true and in inside. 
       { 
        return messageText = "Task stopped."; //4-) And so return and exit the code and task. 
       } 
       if (Connected) 
       { 
       //Do stuff. 
       } 
       if (count == 19) 
       { 
       //Do stuff. 
       } 
      } 
      return messageText; 
     } 
    } 
} 

E un'altra classe della chiamata al metodo:

namespace Blokick.Views 
{ 
    [XamlCompilation(XamlCompilationOptions.Compile)] 
    public partial class MessagePerson : ContentPage 
    { 
     SignalRConnectProvider signalR = new SignalRConnectProvider(); 

     public MessagePerson() 
     { 
      InitializeComponent(); 

      signalR.IsStopRequested = true; // 2-) And this. Make true if running the task and go inside if statement of the IsStopRequested property. 

      if (signalR.ChatHubProxy != null) 
      { 
       signalR.Disconnect(); 
      } 

      LoadSignalRMessage(); 
     } 
    } 
} 
Problemi correlati