Как отменить / отменить задачи TPL?

156

В потоке я создаю некоторые System.Threading.Taskи запускаю каждую задачу.

Когда я делаю, .Abort()чтобы убить поток, задачи не прерываются.

Как я могу передать .Abort()свои задачи?

Патрис Пезилье
источник

Ответы:

228

Ты не можешь Задачи используют фоновые потоки из пула потоков. Также не рекомендуется отменять темы с помощью метода Abort. Вы можете взглянуть на следующий пост в блоге, который объясняет правильный способ отмены задач с использованием токенов отмены. Вот пример:

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
Хорошее объяснение. У меня есть вопрос, как это работает, когда у нас нет анонимного метода в Task.Factory.StartNew? как Task.Factory.StartNew (() => ProcessMyMethod (), cancellationToken)
Prerak K
61
Что делать, если есть блокирующий вызов, который не возвращается внутри выполняемой задачи?
mehmet6parmak
3
@ mehmet6parmak Я думаю, что единственное, что вы можете сделать, это использовать его, Task.Wait(TimeSpan / int)чтобы назначить (основанный на времени) крайний срок извне.
Марк
2
Что если у меня есть свой собственный класс для управления выполнением методов внутри нового Task? Что - то вроде: public int StartNewTask(Action method). Внутри StartNewTaskметода я создаю новый Taskпо: Task task = new Task(() => method()); task.Start();. Так как я могу управлять CancellationToken? Я также хотел бы знать, если Threadмне нужно реализовать логику, чтобы проверить, есть ли какие-то задачи, которые все еще висят, и поэтому убить их, когда Form.Closing. С Threadsя использую Thread.Abort().
Чеширский кот
Боже, какой плохой пример! Это простое логическое условие, конечно, это первое, что можно попробовать! Но у меня есть функция в отдельной Задаче, выполнение которой может занять много времени, и в идеале она не должна ничего знать о потоке или о чем-то еще. Так как я могу отменить функцию с вашим советом?
Привет-ангел
32

Прерывание задачи легко возможно, если вы захватываете поток, в котором выполняется задача. Вот пример кода, демонстрирующий это:

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

Я использовал Task.Run (), чтобы показать наиболее распространенный вариант использования этого - использование удобства Tasks со старым однопоточным кодом, который не использует класс CancellationTokenSource, чтобы определить, следует ли его отменить или нет.

Флориан Раппл
источник
2
Спасибо за эту идею. Использовал этот подход для реализации тайм-аута для некоторого внешнего кода, который не CancellationTokenподдерживает ...
Кристоф Финк
7
AFAIK thread.abort оставит вас неизвестным о вашем стеке, он может быть недействительным. Я никогда не пробовал, но я предполагаю, что при запуске потока в отдельном домене приложения будет сохранен файл thread.abort! Кроме того, весь поток тратится впустую в вашем решении только для того, чтобы прервать одну задачу. Вам не придется использовать задачи, но потоки в первую очередь. (downvote)
Мартин Мизер
1
Как я уже писал, это решение является последним средством, которое может рассматриваться при определенных обстоятельствах. Конечно, CancellationTokenили даже более простые решения, которые не содержат условий гонки, должны быть рассмотрены. Приведенный выше код только иллюстрирует метод, а не область использования.
Флориан Раппл
8
Я думаю, что этот подход может иметь неизвестные последствия, и я бы не рекомендовал его в производственном коде. Не всегда гарантируется, что задачи выполняются в другом потоке, что означает, что они могут выполняться в том же потоке, что и тот, который их создал, если это решит планировщик (что будет означать, что основной поток будет передан threadлокальной переменной). В вашем коде вы можете в итоге прервать основной поток, а это не то, что вам действительно нужно. Возможно, было бы неплохо проверить, совпадают ли потоки перед прерыванием, если вы настаиваете на прерывании
Ивайло Славов
10
@Martin - Поскольку я исследовал этот вопрос на SO, я вижу, что вы отклонили несколько ответов, которые используют Thread.Abort для уничтожения задач, но вы не предоставили никаких альтернативных решений. Как можно убить сторонний код, который не поддерживает отмену и выполняется в задании ?
Гордон Бин
32

Как следует из этого поста , это можно сделать следующим образом:

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

Хотя это работает, не рекомендуется использовать такой подход. Если вы можете контролировать код, который выполняется в задаче, вам лучше использовать правильную обработку отмены.

starteleport
источник
6
+1 за то, что дал другой подход, указав свои запасные варианты. Я не знал, что это можно сделать :)
Джоэл
1
Спасибо за решение! Мы можем просто передать token методу и отменить tokensource вместо того, чтобы каким-либо образом получить экземпляр потока из метода и напрямую прервать его.
Сэм
Поток задачи, который прерывается, может быть потоком пула потоков или потоком вызывающей стороны, вызывающей задачу. Чтобы избежать этого, вы можете использовать TaskScheduler, чтобы указать отдельный поток для задачи .
Эдвард Брей
19

Подобные вещи являются одной из материально-технических причин, почему Abortэто устарело. Прежде всего, не используйте Thread.Abort()для отмены или остановки потока, если это вообще возможно. Abort()следует использовать только для принудительного уничтожения потока, который не отвечает на более мирные запросы о своевременной остановке.

При этом вам необходимо предоставить общий индикатор отмены, который один поток устанавливает и ожидает, пока другой поток периодически проверяет и корректно завершает работу. .NET 4 включает структуру, разработанную специально для этой цели CancellationToken.

Адам Робинсон
источник
8

Вы не должны пытаться сделать это напрямую. Создайте свои задачи для работы с CancellationToken и отмените их таким образом.

Кроме того, я бы порекомендовал изменить основной поток, чтобы он функционировал через CancellationToken. Звонить Thread.Abort()- плохая идея - это может привести к различным проблемам, которые очень сложно диагностировать. Вместо этого, что поток может использовать один и тот же Аннулирование , что ваши задачи использовать - и то же самое CancellationTokenSourceможет быть использован , чтобы вызвать отмену всех ваших задач и ваш основной поток.

Это приведет к гораздо более простому и безопасному дизайну.

Рид Копси
источник
8

Чтобы ответить на вопрос Prerak K о том, как использовать CancellationTokens, когда не используется анонимный метод в Task.Factory.StartNew (), вы передаете CancellationToken в качестве параметра в метод, который вы начинаете с StartNew (), как показано в примере MSDN сюда .

например

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

Я использую смешанный подход, чтобы отменить задачу.

  • Во-первых, я пытаюсь отменить это вежливо с помощью отмены .
  • Если он все еще работает (например, из-за ошибки разработчика), то ведите себя плохо и убивайте его, используя метод Abort старой школы .

Проверьте пример ниже:

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

Задачи имеют первоклассную поддержку отмены через токены отмены . Создайте свои задачи с токенами отмены, и отмените задачи через них явно.

Тим Ллойд
источник
4

Вы можете использовать CancellationTokenдля контроля отмены задачи. Вы говорите о том, чтобы прервать его до его начала («неважно, я уже сделал это») или фактически прерываете его посередине? Если первое, то CancellationTokenможет быть полезно; в последнем случае вам, вероятно, потребуется реализовать свой собственный механизм «спасения» и проверить в соответствующих точках выполнения задачи, следует ли вам быстро потерпеть неудачу (вы все равно можете использовать CancellationToken, чтобы помочь вам, но это немного больше ручного управления).

В MSDN есть статья об отмене задач: http://msdn.microsoft.com/en-us/library/dd997396.aspx

моток
источник
3

Задачи выполняются в ThreadPool (по крайней мере, если вы используете фабрику по умолчанию), поэтому прерывание потока не может повлиять на задачи. Для отмены задач см. Отмена задач на msdn.

Оливер Ханаппи
источник
1

Я пытался, CancellationTokenSourceно я не могу этого сделать. И я сделал это по-своему. И это работает.

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

И еще один класс вызова метода:

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

Вы можете прервать задачу как поток, если можете заставить задачу быть созданной в ее собственном потоке и вызвать Abortее Threadобъект. По умолчанию задача выполняется в потоке пула потоков или в вызывающем потоке - ни один из которых вы обычно не хотите прерывать.

Чтобы задача получила собственный поток, создайте собственный планировщик, полученный из TaskScheduler. В вашей реализации QueueTaskсоздайте новый поток и используйте его для выполнения задачи. Позже вы можете прервать поток, что приведет к завершению задачи в ошибочном состоянии с помощью ThreadAbortException.

Используйте этот планировщик задач:

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
}

Начните свою задачу так:

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

Позже вы можете прервать с помощью:

scheduler.TaskThread.Abort();

Обратите внимание, что предупреждение об прерывании потока все еще применяется:

Thread.AbortМетод следует использовать с осторожностью. В частности, когда вы вызываете его для прерывания потока, отличного от текущего потока, вы не знаете, какой код был выполнен или не удалось выполнить, когда выдается исключение ThreadAbortException , а также вы не можете быть уверены в состоянии вашего приложения или любого приложения и пользовательского состояния. что он отвечает за сохранение. Например, вызов Thread.Abortможет препятствовать выполнению статических конструкторов или препятствовать освобождению неуправляемых ресурсов.

Эдвард Брей
источник
Этот код завершается с исключением во время выполнения: System.InvalidOperationException: RunSynchronously не может быть вызван для задачи, которая уже была запущена.
Теодор Зулиас
1
@ TheodorZoulias Хороший улов. Спасибо. Я исправил код и в целом улучшил ответ.
Эдвард Брей
1
Да, это исправило ошибку. Еще одна оговорка, о которой следует упомянуть, это то, что Thread.Abortона не поддерживается в .NET Core. Попытка его использования приводит к исключению: System.PlatformNotSupportedException: прерывание потока не поддерживается на этой платформе. Третье предостережение заключается в том, что SingleThreadTaskSchedulerего нельзя эффективно использовать с задачами в стиле обещания, другими словами, с задачами, созданными с asyncделегатами. Например, встроенное выполнение await Task.Delay(1000)выполняется без потока, поэтому оно не подвержено влиянию событий потока.
Теодор Зулиас