Come interrompere / annullare le attività TPL?


Risposte:


228

Non puoi. Le attività utilizzano thread in background dal pool di thread. Si sconsiglia inoltre di annullare le discussioni utilizzando il metodo Abort. Puoi dare un'occhiata al seguente post sul blog che spiega un modo corretto di annullare le attività utilizzando 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();
    }
}

5
Bella spiegazione. Ho una domanda, come funziona quando non abbiamo un metodo anonimo in Task.Factory.StartNew? come Task.Factory.StartNew (() => ProcessMyMethod (), cancellazioneToken)
Prerak K

61
cosa succede se è presente una chiamata di blocco che non ritorna all'interno dell'attività in esecuzione?
mehmet6parmak,

3
@ mehmet6parmak Penso che l'unica cosa che puoi fare sia usare Task.Wait(TimeSpan / int)per dargli una scadenza (basata sul tempo) dall'esterno.
Segna il

2
E se avessi la mia classe personalizzata per gestire l'esecuzione di metodi all'interno di una nuova Task? Qualcosa come: public int StartNewTask(Action method). All'interno del StartNewTaskmetodo creo un nuovo Taskda: Task task = new Task(() => method()); task.Start();. Quindi, come posso gestire CancellationToken? Vorrei anche sapere se Threadho bisogno di implementare una logica per verificare se ci sono alcune attività che sono ancora in sospeso e quindi ucciderle quando Form.Closing. Con Threadsuso Thread.Abort().
Cheshire Cat,

Oddio, che cattivo esempio! È una semplice condizione booleana, ovviamente è la prima cosa che si dovrebbe provare! Ma cioè ho una funzione in un compito separato, che potrebbe richiedere molto tempo per terminare, e idealmente non dovrebbe sapere nulla su un threading o altro. Quindi, come posso annullare la funzione con il tuo consiglio?
Ciao Angelo

32

L'annullamento di un'attività è facilmente possibile se si acquisisce il thread in cui è in esecuzione l'attività. Ecco un codice di esempio per dimostrarlo:

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.Run () 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 CancelTokenSource per determinare se debba essere annullato o meno.


2
Grazie per questa idea Utilizzato questo approccio per implementare un timeout per un codice esterno, che non ha CancellationTokensupporto ...
Christoph Fink,

7
AFAIK thread.abort ti lascerà sconosciuto sul tuo stack, potrebbe non essere valido. Non l'ho mai provato, ma immagino che l'avvio di un thread nel dominio app separato che thread.abort verrà salvato! Inoltre, nella tua soluzione viene sprecato un intero thread solo per interrompere un'attività. Non dovresti usare attività ma discussioni in primo luogo. (downvote)
Martin Meeser,

1
Come ho scritto, questa soluzione è l'ultima risorsa che potrebbe essere considerata in determinate circostanze. Ovviamente si CancellationTokendovrebbe prendere in considerazione una o anche soluzioni più semplici che siano prive di condizioni di gara. Il codice sopra illustra solo il metodo, non l'area di utilizzo.
Florian Rappl,

8
Penso che questo approccio possa avere conseguenze sconosciute e non lo consiglierei nel codice di produzione. Non è sempre garantito che le attività vengano eseguite in un thread diverso, il che significa che possono essere eseguite nello stesso thread di quello che li ha creati se lo scheduler lo decide (il che significa che il thread principale verrà passato alla threadvariabile locale). Nel tuo codice potresti finire per interrompere il thread principale, che non è quello che vuoi davvero. Forse un controllo se i fili sono gli stessi prima che l'interruzione sarebbe una buona idea, se insisti ad abortire
Ivaylo Slavov

10
@Martin - Mentre ho studiato questa domanda su SO, vedo che hai votato in negativo diverse risposte che usano Thread.Abort per uccidere le attività, ma non hai fornito soluzioni alternative. Come è possibile eliminare il codice di terze parti che non supporta la cancellazione ed è in esecuzione in un'attività ?
Gordon Bean,

32

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

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

Sebbene funzioni, non è consigliabile utilizzare tale approccio. Se riesci a controllare il codice che viene eseguito nell'attività, è meglio andare con la corretta gestione della cancellazione.


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

1
Grazie per la soluzione! Possiamo semplicemente passare il token al metodo e annullare tokensource, anziché in qualche modo ottenere l'istanza di thread dal metodo e interrompere direttamente questa istanza.
Sam,

Il thread dell'attività che viene interrotto può essere un thread del pool di thread o il thread del chiamante che richiama l'attività. Per evitare ciò, è possibile utilizzare un TaskScheduler per specificare un thread dedicato per l'attività .
Edward Brey,

19

Questo genere di cose è uno dei motivi logistici per cui Abortè deprecato. Innanzitutto, non utilizzare Thread.Abort()per annullare o interrompere una discussione, se possibile. Abort()dovrebbe essere usato solo per uccidere forzatamente un thread che non risponde a richieste più pacifiche per fermarsi in modo tempestivo.

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


8

Non dovresti provare a farlo direttamente. Progetta le tue attività in modo che funzionino con una cancellazione annullata e annullale in questo modo.

Inoltre, consiglierei di modificare il thread principale affinché funzioni anche tramite un CancelToken. Chiamare Thread.Abort()è una cattiva idea: può portare a vari problemi che sono molto difficili da diagnosticare. Al contrario, quel thread può utilizzare la stessa Annullamento utilizzato dalle attività dell'utente e lo stesso CancellationTokenSourcepuò essere utilizzato per attivare l'annullamento di tutte le attività e il thread principale.

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


8

Per rispondere alla domanda di Prerak K su come utilizzare CancelTokens quando non si utilizza un metodo anonimo in Task.Factory.StartNew (), si passa a CancelToken come parametro nel metodo che si inizia con StartNew (), come mostrato nell'esempio MSDN qUI .

per esempio

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, 

}

7

Uso un approccio misto per annullare un'attività.

  • In primo luogo, sto cercando di annullarlo educatamente con l'utilizzo della cancellazione .
  • Se è ancora in esecuzione (ad esempio a causa di un errore dello sviluppatore), comportarsi in modo errato e ucciderlo utilizzando un metodo Abort di vecchia scuola .

Guarda un esempio di seguito:

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

4

Le attività hanno un supporto di prima classe per la cancellazione tramite token di cancellazione . Crea le tue attività con i token di annullamento e annulla le attività in modo esplicito.


4

È possibile utilizzare a CancellationTokenper controllare se l'attività viene annullata. Stai parlando di interromperlo prima che inizi ("non importa, l'ho già fatto") o di interromperlo nel mezzo? Se il primo, il CancellationTokenpuò essere utile; in quest'ultimo caso, sarà probabilmente necessario implementare il proprio meccanismo di "salvataggio" e verificare nei punti appropriati dell'esecuzione dell'attività se si dovrebbe fallire rapidamente (è comunque possibile utilizzare il comando CancelToken per aiutarti, ma è un po 'più manuale).

MSDN ha un articolo sull'annullamento delle attività: http://msdn.microsoft.com/en-us/library/dd997396.aspx


3

Le attività vengono eseguite sul ThreadPool (almeno, se si utilizza la factory predefinita), quindi l'interruzione del thread non può influire sulle attività. Per interrompere le attività, vedere Annullamento attività su msdn.


1

Ci ho provato CancellationTokenSourcema 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 di chiamare il 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();
        }
    }
}

0

È possibile interrompere un'attività come un thread se è possibile che l'attività venga creata sul proprio thread e che venga richiamato Abortsul suo Threadoggetto. Per impostazione predefinita, un'attività viene eseguita su un thread del pool di thread o sul thread chiamante, nessuno dei quali in genere si desidera interrompere.

Per assicurarsi che l'attività ottenga il proprio thread, creare uno scheduler personalizzato derivato TaskScheduler. Nella tua implementazione di QueueTask, crea un nuovo thread e usalo per eseguire l'attività. Successivamente, è possibile interrompere il thread, che comporterà il completamento dell'attività in uno stato di errore con a ThreadAbortException.

Utilizzare questo Utilità di pianificazione:

class SingleThreadTaskScheduler : TaskScheduler
{
    public Thread TaskThread { get; private set; }

    protected override void QueueTask(Task task)
    {
        TaskThread = new Thread(() => TryExecuteTask(task));
        TaskThread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException(); // Unused
    protected override bool NotSupportedException(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException(); // Unused
}

Inizia l'attività in questo modo:

var scheduler = new SingleThreadTaskScheduler();
var task = Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.LongRunning, scheduler);

Successivamente, puoi interrompere con:

scheduler.TaskThread.Abort();

Si noti che l' avvertenza sull'interruzione di un thread rimane valida:

Il Thread.Abortmetodo deve essere usato con cautela. Soprattutto quando lo chiami per interrompere un thread diverso dal thread corrente, non sai quale codice è stato eseguito o non è stato eseguito quando viene lanciata ThreadAbortException , né puoi essere certo dello stato della tua applicazione o di qualsiasi stato dell'applicazione e utente che è responsabile per la conservazione. Ad esempio, la chiamata Thread.Abortpuò impedire l'esecuzione di costruttori statici o il rilascio di risorse non gestite.


Questo codice ha esito negativo con un'eccezione di runtime: System.InvalidOperationException: RunSynchronously potrebbe non essere chiamato su un'attività che era già stata avviata.
Theodor Zoulias,

1
@TheodorZoulias Buona cattura. Grazie. Ho corretto il codice e generalmente migliorato la risposta.
Edward Brey,

1
Sì, questo ha corretto l'errore. Un altro avvertimento che probabilmente dovrebbe essere menzionato è che Thread.Abortnon è supportato su .NET Core. Tentare di usarlo comporta un'eccezione: System.PlatformNotSupportedException: l'interruzione del thread non è supportata su questa piattaforma. Un terzo avvertimento è che SingleThreadTaskSchedulernon può essere utilizzato in modo efficace con attività di tipo promettente, in altre parole con attività create con i asyncdelegati. Ad esempio, un incorporato await Task.Delay(1000)viene eseguito senza thread, quindi non è interessato dagli eventi thread.
Theodor Zoulias,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.