Wie kann ich TPL-Aufgaben abbrechen / abbrechen?

Antworten:

228

Das kannst du nicht. Aufgaben verwenden Hintergrundthreads aus dem Threadpool. Das Abbrechen von Threads mit der Abort-Methode wird ebenfalls nicht empfohlen. Sie können sich den folgenden Blog-Beitrag ansehen, in dem erläutert wird, wie Aufgaben mithilfe von Stornierungs-Token ordnungsgemäß abgebrochen werden. Hier ist ein Beispiel:

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();
    }
}
Darin Dimitrov
quelle
5
Schöne Erklärung. Ich habe eine Frage, wie funktioniert es, wenn wir keine anonyme Methode in Task.Factory.StartNew haben? wie Task.Factory.StartNew (() => ProcessMyMethod (), CancellationToken)
Prerak K
61
Was ist, wenn es einen blockierenden Aufruf gibt, der innerhalb der ausgeführten Aufgabe nicht zurückkehrt?
mehmet6parmak
3
@ mehmet6parmak Ich denke, das einzige, was Sie dann tun können, ist Task.Wait(TimeSpan / int), ihm eine (zeitbasierte) Frist von außen zu geben.
Mark
2
Was ist, wenn ich meine benutzerdefinierte Klasse habe, um die Ausführung von Methoden in einer neuen zu verwalten Task? So etwas wie : public int StartNewTask(Action method). Innerhalb der StartNewTaskMethode erstelle ich eine neue Taskvon : Task task = new Task(() => method()); task.Start();. Wie kann ich das verwalten CancellationToken? Ich würde auch gerne wissen, ob Threadich eine Logik implementieren muss, um zu überprüfen, ob noch einige Aufgaben hängen, und sie dann zu töten, wenn Form.Closing. Mit Threadsich benutze Thread.Abort().
Cheshire Cat
Omg, was für ein schlechtes Beispiel! Es ist eine einfache boolesche Bedingung, natürlich ist es die erste, die man versuchen würde! Aber dh ich habe eine Funktion in einer separaten Aufgabe, deren Beendigung viel Zeit in Anspruch nehmen kann, und im Idealfall sollte sie nichts über ein Threading oder was auch immer wissen. Wie kann ich die Funktion mit Ihrem Rat abbrechen?
Hi-Angel
32

Das Abbrechen einer Aufgabe ist problemlos möglich, wenn Sie den Thread erfassen, in dem die Aufgabe ausgeführt wird. Hier ein Beispielcode, um dies zu demonstrieren:

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

Ich habe Task.Run () verwendet, um den häufigsten Anwendungsfall dafür anzuzeigen - unter Verwendung des Komforts von Aufgaben mit altem Single-Threaded-Code, bei dem die CancellationTokenSource-Klasse nicht verwendet wird, um zu bestimmen, ob sie abgebrochen werden soll oder nicht.

Florian Rappl
quelle
2
Danke für diese Idee. Verwendete diesen Ansatz, um eine Zeitüberschreitung für einen externen Code zu implementieren, der keine CancellationTokenUnterstützung hat ...
Christoph Fink
7
AFAIK thread.abort lässt Sie über Ihren Stapel unbekannt, es könnte ungültig sein. Ich habe es noch nie versucht, aber ich denke, ein Thread in einer separaten App-Domain zu starten, der thread.abort gespeichert wird! Außerdem wird in Ihrer Lösung ein ganzer Thread verschwendet, um nur eine Aufgabe abzubrechen. Sie müssten zunächst keine Aufgaben, sondern Threads verwenden. (Downvote)
Martin Meeser
1
Wie ich schrieb - diese Lösung ist ein letzter Ausweg, der unter bestimmten Umständen in Betracht gezogen werden könnte. Natürlich sollten eine CancellationTokenoder noch einfachere Lösungen in Betracht gezogen werden, die frei von Rennbedingungen sind. Der obige Code veranschaulicht nur die Methode, nicht den Anwendungsbereich.
Florian Rappl
8
Ich denke, dieser Ansatz könnte unbekannte Konsequenzen haben und ich würde ihn im Produktionscode nicht empfehlen. Es wird nicht immer garantiert, dass Aufgaben in einem anderen Thread ausgeführt werden. Dies bedeutet, dass sie in demselben Thread ausgeführt werden können, in dem sie erstellt wurden, wenn der Scheduler dies entscheidet (was bedeutet, dass der Hauptthread an die threadlokale Variable übergeben wird). In Ihrem Code wird der Hauptthread möglicherweise abgebrochen, was nicht wirklich gewünscht ist. Vielleicht wäre eine Überprüfung, ob die Threads vor dem Abbruch gleich sind, eine gute Idee, wenn Sie darauf bestehen, abzubrechen
Ivaylo Slavov
10
@Martin - Da ich diese Frage zu SO recherchiert habe, habe ich festgestellt, dass Sie mehrere Antworten, die Thread.Abort zum Beenden von Aufgaben verwenden, abgelehnt haben, aber keine alternativen Lösungen bereitgestellt haben. Wie können Sie Code von Drittanbietern beenden, der die Stornierung nicht unterstützt und in einer Aufgabe ausgeführt wird ?
Gordon Bean
32

Wie in diesem Beitrag vorgeschlagen , kann dies folgendermaßen erfolgen:

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

Obwohl es funktioniert, wird nicht empfohlen, einen solchen Ansatz zu verwenden. Wenn Sie den Code steuern können, der in der Aufgabe ausgeführt wird, sollten Sie die Stornierung ordnungsgemäß behandeln.

Starteleport
quelle
6
+1 für einen anderen Ansatz bei der Angabe der Fallbacks. Ich wusste nicht, dass dies möglich ist :)
Joel
1
Danke für die Lösung! Wir können das Token einfach an die Methode übergeben und die Tokenquelle abbrechen, anstatt irgendwie die Thread-Instanz von der Methode abzurufen und diese Instanz direkt abzubrechen.
Sam
Der abgebrochene Thread der Aufgabe kann ein Thread-Pool-Thread oder der Thread des Aufrufers sein, der die Aufgabe aufruft. Um dies zu vermeiden, können Sie mit einem TaskScheduler einen dedizierten Thread für die Aufgabe angeben .
Edward Brey
19

Diese Art von Dingen ist einer der logistischen Gründe, warum sie Abortveraltet sind. Verwenden Sie Thread.Abort()in erster Linie nicht , um einen Thread abzubrechen oder zu stoppen, wenn dies überhaupt möglich ist. Abort()sollte nur verwendet werden, um einen Thread gewaltsam zu beenden, der nicht auf friedlichere Anfragen reagiert, rechtzeitig anzuhalten.

Davon abgesehen müssen Sie einen gemeinsamen Abbruchindikator bereitstellen, den ein Thread setzt und wartet, während der andere Thread regelmäßig prüft und ordnungsgemäß beendet. .NET 4 enthält eine speziell für diesen Zweck entwickelte Struktur, die CancellationToken.

Adam Robinson
quelle
8

Sie sollten dies nicht direkt versuchen. Entwerfen Sie Ihre Aufgaben so, dass sie mit einem CancellationToken arbeiten , und brechen Sie sie auf diese Weise ab.

Außerdem würde ich empfehlen, Ihren Hauptthread so zu ändern, dass er auch über ein CancellationToken funktioniert. Anrufen Thread.Abort()ist eine schlechte Idee - es kann zu verschiedenen Problemen führen, die sehr schwer zu diagnostizieren sind. Stattdessen kann dieser Thread dieselbe Stornierung verwenden , die Ihre Aufgaben verwenden - und dieselbe CancellationTokenSourcekann verwendet werden, um die Stornierung aller Ihrer Aufgaben und Ihres Hauptthreads auszulösen .

Dies führt zu einem weitaus einfacheren und sichereren Design.

Reed Copsey
quelle
8

Um die Frage von Prerak K zur Verwendung von CancellationTokens zu beantworten, wenn in Task.Factory.StartNew () keine anonyme Methode verwendet wird, übergeben Sie das CancellationToken als Parameter an die Methode, die Sie mit StartNew () beginnen, wie im MSDN-Beispiel gezeigt Hier .

z.B

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, 

}
Jools
quelle
7

Ich benutze einen gemischten Ansatz, um eine Aufgabe abzubrechen.

  • Erstens versuche ich es höflich mit der Stornierung abzubrechen .
  • Wenn es noch läuft (z. B. aufgrund eines Entwicklerfehlers), verhalten Sie sich schlecht und beenden Sie es mit einer Abort- Methode der alten Schule .

Kasse ein Beispiel unten:

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();
        }
    }
}
Alex Klaus
quelle
4

Aufgaben bieten erstklassige Unterstützung für die Stornierung über Stornierungs-Token . Erstellen Sie Ihre Aufgaben mit Abbruchtoken und brechen Sie die Aufgaben über diese explizit ab.

Tim Lloyd
quelle
4

Mit a können Sie CancellationTokensteuern, ob die Aufgabe abgebrochen wird. Sprechen Sie darüber, es abzubrechen, bevor es gestartet wird ("egal, ich habe das bereits getan") oder es tatsächlich in der Mitte zu unterbrechen? Wenn erstere, CancellationTokenkann das hilfreich sein; In letzterem Fall müssen Sie wahrscheinlich Ihren eigenen "Bail-out" -Mechanismus implementieren und an geeigneten Stellen in der Aufgabenausführung prüfen, ob Sie schnell fehlschlagen sollten (Sie können das CancellationToken weiterhin als Hilfe verwenden, es ist jedoch etwas manueller).

MSDN hat einen Artikel zum Abbrechen von Aufgaben: http://msdn.microsoft.com/en-us/library/dd997396.aspx

Strang
quelle
3

Aufgaben werden auf dem ThreadPool ausgeführt (zumindest wenn Sie die Standardfactory verwenden), sodass das Abbrechen des Threads keine Auswirkungen auf die Aufgaben haben kann. Für Aufgaben Abbruch findet Aufgabenabbruch auf msdn.

Oliver Hanappi
quelle
1

Ich habe es versucht, CancellationTokenSourceaber ich kann das nicht tun. Und das habe ich auf meine eigene Weise gemacht. Und es funktioniert.

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

Und eine andere Klasse des Aufrufs der Methode:

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();
        }
    }
}
Hasan Thunfisch Oruç
quelle
0

Sie können eine Aufgabe wie ein roter Faden abbrechen , wenn die Aufgabe auf einem eigenen Thread erstellt dazu führen kann , und rufen Abortauf ihrenThread Objekt . Standardmäßig wird eine Aufgabe in einem Thread-Pool-Thread oder dem aufrufenden Thread ausgeführt, von denen Sie normalerweise keinen abbrechen möchten.

Erstellen Sie einen benutzerdefinierten Scheduler, von dem abgeleitet wird, um sicherzustellen, dass die Aufgabe einen eigenen Thread erhält TaskScheduler. QueueTaskErstellen Sie in Ihrer Implementierung von einen neuen Thread und verwenden Sie ihn zum Ausführen der Aufgabe. Später können Sie den Thread abbrechen, wodurch die Aufgabe in einem fehlerhaften Zustand mit a abgeschlossen wird ThreadAbortException.

Verwenden Sie diesen Taskplaner:

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
}

Beginnen Sie Ihre Aufgabe wie folgt:

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

Später können Sie abbrechen mit:

scheduler.TaskThread.Abort();

Beachten Sie, dass die Einschränkung zum Abbrechen eines Threads weiterhin gilt:

Die Thread.AbortMethode sollte mit Vorsicht angewendet werden. Insbesondere wenn Sie es aufrufen, um einen anderen Thread als den aktuellen Thread abzubrechen, wissen Sie nicht, welcher Code ausgeführt wurde oder nicht ausgeführt werden konnte, wenn die ThreadAbortException ausgelöst wird, und Sie können sich auch nicht über den Status Ihrer Anwendung oder einen Anwendungs- und Benutzerstatus sicher sein dass es für die Erhaltung verantwortlich ist. Beispielsweise kann ein Aufruf Thread.Abortdie Ausführung statischer Konstruktoren verhindern oder die Freigabe nicht verwalteter Ressourcen verhindern.

Edward Brey
quelle
Dieser Code schlägt mit einer Laufzeitausnahme fehl: System.InvalidOperationException: RunSynchronously kann möglicherweise nicht für eine bereits gestartete Task aufgerufen werden.
Theodor Zoulias
1
@ TheodorZoulias Guter Fang. Vielen Dank. Ich habe den Code korrigiert und die Antwort allgemein verbessert.
Edward Brey
1
Ja, das hat den Fehler behoben. Eine weitere Einschränkung, die wahrscheinlich erwähnt werden sollte, ist, dass Thread.Abortsie unter .NET Core nicht unterstützt wird. Der Versuch, es dort zu verwenden, führt zu einer Ausnahme: System.PlatformNotSupportedException: Thread-Abbruch wird auf dieser Plattform nicht unterstützt. Eine dritte Einschränkung ist, dass das SingleThreadTaskSchedulernicht effektiv mit Aufgaben im Versprechungsstil verwendet werden kann, dh mit Aufgaben, die mit asyncDelegierten erstellt wurden. Beispielsweise wird ein Embedded await Task.Delay(1000)in keinem Thread ausgeführt, sodass Thread-Ereignisse davon nicht betroffen sind.
Theodor Zoulias