Як відмінити / скасувати завдання TPL?


156

У нитці я створюю деякі System.Threading.Taskі запускаю кожне завдання.

Коли я роблю a, .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().
Cheshire Cat

Омг, який поганий приклад! Це проста булева ситуація, звичайно, це перше, що б спробувати! Але я маю функцію в окремому завданні, яке може зайняти багато часу, щоб закінчитися, і в ідеалі воно не повинно знати нічого про нарізку або інше. Тож як я можу скасувати функцію за допомогою ваших порад?
Привіт-Ангел

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 (), щоб показати найпоширеніший випадок використання для цього - використовуючи комфорт Завдання зі старим однопотоковим кодом, який не використовує клас CancellationTokenSource, щоб визначити, чи слід його скасувати чи ні.


2
Дякую за цю ідею. Використовували цей підхід для впровадження тайм-ауту для деякого зовнішнього коду, який не CancellationTokenпідтримує ...
Крістоф Фінк

7
AFAIK thread.abort залишить вас невідомими про ваш стек, він може бути недійсним. Я ніколи не пробував цього, але, мабуть, запустив нитку в окремий домен додатка, що thread.abort буде врятовано! Крім того, у вашому рішенні витрачається ціла нитка лише для того, щоб скасувати одне завдання. Вам не доведеться використовувати завдання, але теми в першу чергу. (downvote)
Мартін Мізер

1
Як я писав - це рішення є крайнім засобом, який може бути розглянутий за певних обставин. Зрозуміло, CancellationTokenслід враховувати або навіть більш прості рішення, які не належать до перегонів. Код, наведений вище, ілюструє лише метод, а не область використання.
Флоріан Раппл

8
Я думаю, що такий підхід може мати невідомі наслідки, і я б не рекомендував його у виробничому коді. Завдання не завжди гарантуються виконанням в іншому потоці, тобто вони можуть бути запущені в тому ж потоці, що і той, що їх створив, якщо планувальник вирішить так (що означатиме, що основний потік буде переданий до threadлокальної змінної). Тоді у вашому коді ви можете перервати основну нитку, а це не те, чого ви дійсно хочете. Можливо, перевірити, чи нитки є однаковими перед абортом, було б добре подумати, якщо ви наполягаєте на перериванні вагітності
Івайло Славов

10
@Martin - Коли я досліджував це питання на ЗО, я бачу, що ви заперечили кілька відповідей, які використовують Thread.Abort для вбивства завдань, але ви не запропонували жодних альтернативних рішень. Як можна вбити код третьої сторони, який не підтримує скасування та працює у завданні ?
Гордон Бін

32

Як передбачає ця публікація , це можна зробити наступним чином:

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

Хоча це працює, застосовувати такий підхід не рекомендується. Якщо ви можете керувати кодом, який виконується у завданні, вам краще перейти до правильної обробки скасування.


6
+1 за надання іншого підходу, констатуючи його запасні місця. Я не знав, що це можна зробити :)
Джоель

1
Дякуємо за рішення! Ми можемо просто передати токен методу та скасувати токенсор, замість того, щоб якось отримати екземпляр потоку з методу та перервати цей екземпляр безпосередньо.
Сем

Потік завдання, який перервано, може бути потоком пулу потоків або потоком абонента, який викликає завдання. Щоб цього уникнути, ви можете скористатися програмою 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

Ви можете використовувати a, 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створіть новий потік і використовуйте його для виконання завдання. Пізніше ви можете перервати нитку, що призведе до завершення завдання у несправному стані з a 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: RunSynchronically не може викликатися завданням, яке вже було запущено.
Теодор Зуліяс

1
@TheodorZoulias Хороший улов. Дякую. Я виправив код і загалом покращив відповідь.
Едвард Брей

1
Так, це виправило помилку. Ще одне застереження, яке слід, мабуть, згадати, - це те, що Thread.Abortвін не підтримується у .NET Core. Спроба використовувати його призводить до виключення: System.PlatformNotSupportedException: Переривання теми не підтримується на цій платформі. Третє застереження полягає в тому, що SingleThreadTaskSchedulerне можна ефективно використовувати завдання із стилем обіцянок, іншими словами, із завданнями, створеними з asyncделегатами. Наприклад, вбудована програма не await Task.Delay(1000)працює в потоці, тому це не впливає на події потоку.
Теодор Зуліяс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.