Как прервать / отменить Задачи TPL? - PullRequest
140 голосов
/ 24 января 2011

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

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

Как можноЯ передаю .Abort() своим задачам?

Ответы [ 11 ]

210 голосов
/ 24 января 2011

Вы не можете. Задачи используют фоновые потоки из пула потоков. Также не рекомендуется отменять темы с помощью метода 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();
    }
}
27 голосов
/ 07 февраля 2013

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

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 для определения, следует ли его отменить или нет.

26 голосов
/ 11 октября 2013

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

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

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

17 голосов
/ 24 января 2011

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

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

7 голосов
/ 14 октября 2014

Чтобы ответить на вопрос Прерак К. о том, как использовать 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 голосов
/ 24 января 2011

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

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

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

6 голосов
/ 06 августа 2014

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

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

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

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();
        }
    }
}
3 голосов
/ 24 января 2011

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

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

2 голосов
/ 24 января 2011

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

2 голосов
/ 24 января 2011

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

...