Jak przerwać / anulować zadania OC?

Odpowiedzi:

228

Nie możesz. Zadania używają wątków w tle z puli wątków. Nie zaleca się również anulowania wątków metodą Abort. Możesz rzucić okiem na następujący post na blogu, który wyjaśnia właściwy sposób anulowania zadań za pomocą tokenów anulowania. Oto przykład:

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
źródło
5
Niezłe wyjaśnienie. Mam pytanie, jak to działa, gdy nie mamy metody anonimowej w Task.Factory.StartNew? jak Task.Factory.StartNew (() => ProcessMyMethod (), cancellationToken)
Prerak K
61
co się stanie, jeśli pojawi się wywołanie blokujące, które nie powróci do wykonywanego zadania?
mehmet6parmak
3
@ mehmet6parmak Myślę, że jedyne, co możesz wtedy zrobić, to Task.Wait(TimeSpan / int)nadać mu (czasowy) termin z zewnątrz.
Mark
2
Co się stanie, jeśli mam własną klasę do zarządzania wykonywaniem metod wewnątrz nowej Task? Coś jak: public int StartNewTask(Action method). Wewnątrz StartNewTaskmetody utworzyć nowy Taskprzez: Task task = new Task(() => method()); task.Start();. Więc jak mogę zarządzać CancellationToken? Chciałbym również wiedzieć, czy Threadmuszę zaimplementować logikę, aby sprawdzić, czy są jakieś zadania, które nadal się zawieszają, i zabij je, kiedy Form.Closing. Z Threadsużywam Thread.Abort().
Cheshire Cat
O mój Boże, co za zły przykład! Jest to prosty warunek logiczny, oczywiście jest to pierwszy, jakiego można by spróbować! Ale tj. Mam funkcję w osobnym zadaniu, której zakończenie może zająć dużo czasu i najlepiej byłoby, gdyby nie wiedziała nic o wątkowaniu lub czymkolwiek. Jak więc anulować tę funkcję za twoją radą?
Hi-Angel
32

Przerwanie zadania jest łatwe, jeśli przechwycisz wątek, w którym zadanie jest uruchomione. Oto przykładowy kod, który to demonstruje:

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

Użyłem Task.Run (), aby pokazać najczęstszy przypadek użycia tego - korzystając z wygody Tasks ze starym kodem jednowątkowym, który nie używa klasy CancellationTokenSource do określenia, czy należy go anulować, czy nie.

Florian Rappl
źródło
2
Dzięki za ten pomysł. Zastosowano to podejście, aby zaimplementować limit czasu dla jakiegoś zewnętrznego kodu, który nie ma CancellationTokenwsparcia ...
Christoph Fink
7
AFAIK thread.abort pozostawi nieznany twój stos, może być nieprawidłowy. Nigdy tego nie próbowałem, ale myślę, że rozpoczynając wątek w oddzielnej domenie aplikacji, ten wątek.abort zostanie zapisany! Poza tym cały wątek jest marnowany w twoim rozwiązaniu tylko po to, aby przerwać jedno zadanie. Nie musiałbyś używać zadań, ale wątków na pierwszym miejscu. (głos przeciwny)
Martin Meeser
1
Jak pisałem - to rozwiązanie to ostateczność, którą można rozważyć w pewnych okolicznościach. Oczywiście CancellationTokennależy wziąć pod uwagę lub nawet prostsze rozwiązania, które są wolne od warunków wyścigowych. Powyższy kod ilustruje tylko metodę, a nie obszar użycia.
Florian Rappl
8
Myślę, że takie podejście może mieć nieznane konsekwencje i nie polecałbym go w kodzie produkcyjnym. Zadania nie zawsze są wykonywane w innym wątku, co oznacza, że ​​można je uruchomić w tym samym wątku, co ten, który je utworzył, jeśli planista tak zdecyduje (co oznacza, że ​​główny wątek zostanie przekazany do threadzmiennej lokalnej). W swoim kodzie możesz zakończyć przerwanie głównego wątku, co nie jest tym, czego naprawdę chcesz. Być może sprawdzenie, czy wątki są takie same przed przerwaniem, byłoby dobrym pomysłem, jeśli nalegasz na przerwanie
Ivaylo Slavov
10
@Martin - Po zbadaniu tego pytania na SO, widzę, że przegłosowałeś kilka odpowiedzi, które używają Thread.Abort do zabijania zadań, ale nie podałeś żadnych alternatywnych rozwiązań. Jak można zabić kod innej firmy, który nie obsługuje anulowania i działa w zadaniu ?
Gordon Bean,
32

Jak sugeruje ten post , można to zrobić w następujący sposób:

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

Chociaż to działa, nie zaleca się stosowania takiego podejścia. Jeśli możesz kontrolować kod, który jest wykonywany w zadaniu, lepiej skorzystaj z odpowiedniej obsługi anulowania.

starteleport
źródło
6
+1 za podanie innego podejścia i wskazanie jego wad. Nie wiedziałem, że można to zrobić :)
Joel
1
Dzięki za rozwiązanie! Możemy po prostu przekazać token do metody i anulować źródło tokenu, zamiast w jakiś sposób pobierać wystąpienie wątku z metody i bezpośrednio przerywać to wystąpienie.
Sam
Wątek zadania, który zostaje przerwany, może być wątkiem puli wątków lub wątkiem obiektu wywołującego wywołującego zadanie. Aby tego uniknąć, możesz użyć TaskScheduler, aby określić dedykowany wątek dla zadania .
Edward Brey
19

Takie rzeczy są jednym z logistycznych powodów, dla których Abortsą przestarzałe. Przede wszystkim nie używaj Thread.Abort()do anulowania lub zatrzymywania wątku, jeśli jest to w ogóle możliwe. Abort()powinien być używany tylko do wymuszonego niszczenia wątku, który nie odpowiada na spokojniejsze prośby o zaprzestanie w odpowiednim czasie.

Biorąc to pod uwagę, musisz podać udostępniony wskaźnik anulowania, który jeden wątek ustawia i czeka, podczas gdy drugi wątek okresowo sprawdza i bezpiecznie kończy pracę. .NET 4 zawiera strukturę zaprojektowaną specjalnie do tego celu CancellationToken.

Adam Robinson
źródło
8

Nie powinieneś próbować tego robić bezpośrednio. Zaprojektuj swoje zadania do pracy z CancellationToken i anuluj je w ten sposób.

Ponadto zalecałbym zmianę głównego wątku, aby działał również za pośrednictwem CancellationToken. Telefonowanie Thread.Abort()to zły pomysł - może prowadzić do różnych problemów, które są bardzo trudne do zdiagnozowania. Zamiast tego ten wątek może używać tego samego anulowania, którego używają twoje zadania - i tego samego CancellationTokenSourcemożna użyć do wyzwolenia anulowania wszystkich zadań i głównego wątku.

Doprowadzi to do znacznie prostszego i bezpieczniejszego projektu.

Reed Copsey
źródło
8

Aby odpowiedzieć na pytanie Prerak K o to, jak używać CancellationTokens, gdy nie używasz metody anonimowej w Task.Factory.StartNew (), musisz przekazać CancellationToken jako parametr do metody, którą zaczynasz od StartNew (), jak pokazano w przykładzie MSDN tutaj .

na przykład

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
źródło
7

Stosuję mieszane podejście do anulowania zadania.

  • Po pierwsze, staram się uprzejmie anulować, korzystając z opcji Anulowanie .
  • Jeśli nadal działa (np. Z powodu błędu programisty), zachowaj się niewłaściwie i zabij go starą metodą Abort .

Sprawdź przykład poniżej:

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
źródło
4

Zadania mają pierwszą klasę obsługi anulowania za pomocą tokenów anulowania . Twórz zadania za pomocą tokenów anulowania i wyraźnie anuluj zadania za pomocą tych tokenów.

Tim Lloyd
źródło
4

Możesz użyć, CancellationTokenaby kontrolować, czy zadanie zostanie anulowane. Czy mówisz o przerwaniu tego przed rozpoczęciem („nieważne, już to zrobiłem”), czy też o przerwaniu w środku? Jeśli to pierwsze, CancellationTokenmogą być pomocne; jeśli to drugie, prawdopodobnie będziesz musiał zaimplementować własny mechanizm "bail out" i sprawdzić w odpowiednich momentach wykonywania zadania, czy powinieneś szybko zawieść (nadal możesz użyć CancellationToken, aby ci pomóc, ale jest to trochę bardziej ręczne).

W witrynie MSDN jest artykuł dotyczący anulowania zadań: http://msdn.microsoft.com/en-us/library/dd997396.aspx

Motek
źródło
3

Zadania są wykonywane w puli wątków (przynajmniej jeśli używasz domyślnej fabryki), więc przerwanie wątku nie ma wpływu na zadania. Aby uzyskać informacje na temat przerywania zadań, zobacz Anulowanie zadań w witrynie msdn.

Oliver Hanappi
źródło
1

Próbowałem, CancellationTokenSourceale nie mogę tego zrobić. I zrobiłem to na swój własny sposób. I to działa.

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

I kolejna klasa wywołująca metodę:

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 Tuna Oruç
źródło
0

Możesz przerwać zadanie tak jak wątek, jeśli możesz spowodować utworzenie zadania we własnym wątku i wywołanie Abortjego Threadobiektu. Domyślnie zadanie jest uruchamiane w wątku puli wątków lub wątku wywołującym - z których zwykle nie chcesz przerwać.

Aby upewnić się, że zadanie otrzymuje własny wątek, utwórz niestandardowy harmonogram pochodzący z TaskScheduler. W swojej implementacji QueueTaskutwórz nowy wątek i użyj go do wykonania zadania. Później możesz przerwać wątek, co spowoduje zakończenie zadania w stanie błędu z rozszerzeniem ThreadAbortException.

Użyj tego harmonogramu zadań:

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
}

Rozpocznij swoje zadanie w ten sposób:

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

Później możesz przerwać za pomocą:

scheduler.TaskThread.Abort();

Pamiętaj, że nadal obowiązuje zastrzeżenie dotyczące przerywania wątku :

Thread.AbortMetoda powinna być stosowana z ostrożnością. W szczególności, gdy wywołujesz go w celu przerwania wątku innego niż bieżący wątek, nie wiesz, jaki kod został wykonany lub którego wykonanie nie powiodło się, gdy zostanie zgłoszony wyjątek ThreadAbortException , ani nie możesz być pewien stanu aplikacji lub stanu aplikacji i użytkownika że jest odpowiedzialny za zachowanie. Na przykład wywołanie Thread.Abortmoże uniemożliwić wykonanie konstruktorów statycznych lub zwolnienie niezarządzanych zasobów.

Edward Brey
źródło
Ten kod kończy się niepowodzeniem z wyjątkiem czasu wykonywania: System.InvalidOperationException: RunSynchronously nie może być wywoływana dla zadania, które zostało już uruchomione.
Theodor Zoulias
1
@TheodorZoulias Dobry chwyt. Dzięki. Poprawiłem kod i ogólnie poprawiłem odpowiedź.
Edward Brey
1
Tak, to naprawiło błąd. Innym zastrzeżeniem, o którym prawdopodobnie należy wspomnieć, jest to, że Thread.Abortnie jest obsługiwane na platformie .NET Core. Próba użycia go tam kończy się wyjątkiem: System.PlatformNotSupportedException: przerywanie wątku nie jest obsługiwane na tej platformie. Trzecim zastrzeżeniem jest to, że SingleThreadTaskSchedulernie można ich skutecznie używać w przypadku zadań typu obietnica, innymi słowy w przypadku zadań tworzonych z asyncdelegatami. Na przykład osadzony await Task.Delay(1000)działa w żadnym wątku, więc nie ma to wpływu na zdarzenia wątku.
Theodor Zoulias