Luffy1
@Luffy1
Student, Junior .NET programmer, C#, JS, HTML/CSS

Почему метод Cancel() объекта cancelTokenSource действует и на объект Task, которому в параметры конструктора токен не передаётся (см. внутри)?

Фрагмент кода в Main():
static void Main(string[] args)
{
    CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
    CancellationToken token = cancelTokenSource.Token;

    Task task = new Task(() =>
    {
        for (int i = 1; i < 10; i++)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Операция прервана");
                return;
            }
            Console.WriteLine($"1. Квадрат числа {i} равен {i * i}");
            Thread.Sleep(200);
        }
    }, token);
    Task task2 = new Task(() =>
    {
        for (int i = 1; i < 10; i++)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Операция прервана");
                return;
            }
            Console.WriteLine($"2. Квадрат числа {i} равен {i * i}");
            Thread.Sleep(200);
        }
    });
    task.Start();
    task2.Start();

    Thread.Sleep(1000);
    cancelTokenSource.Cancel();
    Thread.Sleep(1000);
    Console.WriteLine($"Task Status: {task.Status}");
    Console.WriteLine($"Task Status: {task2.Status}");
    cancelTokenSource.Dispose();
}


Передаю токен в параметры конструктора первого объекта класса Task, но не передаю второму объекту. ПРи этом, на сколько я понимаю логику работы CancellationTokenSource и его использования (могу ошибаться), те таски, которым мы передаём токен, и могут как раз быть отменены конкретно через этот токен. То есть если у мя есть token и таски Task1, Task2, Task3, и я передаю его в параметры конструкторов Task1 и Task3, то и отменить с помощью метода Cancel() я могу только задачи Task1 и Task3, по идее.

Консольный вывод:
2. Квадрат числа 1 равен 1
1. Квадрат числа 1 равен 1
1. Квадрат числа 2 равен 4
2. Квадрат числа 2 равен 4
1. Квадрат числа 3 равен 9
2. Квадрат числа 3 равен 9
2. Квадрат числа 4 равен 16
1. Квадрат числа 4 равен 16
1. Квадрат числа 5 равен 25
2. Квадрат числа 5 равен 25
Операция прервана
Операция прервана
Task Status: RanToCompletion
Task Status: RanToCompletion


Я не понимаю, почему токен будто передаётся и объекту Task2, хотя такого по факту не происходит. Пожалуйста, кто знает, что тут происходит, объясните, почему такой консольный вывод и почему таска№2 тоже останавливается. Заранее благодарю!
  • Вопрос задан
  • 103 просмотра
Решения вопроса 2
mindtester
@mindtester Куратор тега C#
http://iczin.su/hexagram_48
вообще то твой код не работает в дотнет8, при прямом копировании. ошибок не дает, но и ни чего не печатает.. поиграюсь.. а так то... смотри внимательно:
CancellationToken token = cancelTokenSource.Token;
///...
    Task task2 = new Task(() =>
    {
        for (int i = 1; i < 10; i++)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Операция прервана");
                return;
            }
            Console.WriteLine($"2. Квадрат числа {i} равен {i * i}");
            Thread.Sleep(200);
        }
    });
.. они у тебя в одной области видимости, и ты бодро проверяешь статус завершения.. зачем? ;)))
... решения?.. ну простейшее - CancellationTokenSource2 ... не быть же второй таске вечной..

ps
в 4,8 работает так
using System.Threading.Tasks;
using System.Threading;
using System;

class cancelTokenTest
{
    static void Main(string[] args)
    {
        var cancelTokenSource = new CancellationTokenSource();
        var token = cancelTokenSource.Token;

        var cancelTokenSource2 = new CancellationTokenSource();
        var token2 = cancelTokenSource2.Token;

        var task = new Task(() =>
        {
            for (int i = 1; i < 100000; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("Операция 1 прервана");
                    return;
                }
                Console.WriteLine($"1. Квадрат числа {i} равен {i * i}");
                Thread.Sleep(200);
            }
        }, token);

        var task2 = new Task(() =>
        {
            for (int i = 10; i < 1000000; i++)
            {
                if (token2.IsCancellationRequested)
                {
                    Console.WriteLine("Операция 2 прервана");
                    return;
                }
                Console.WriteLine($"2. Квадрат числа {i} равен {i * i}");
                Thread.Sleep(200);
            }
        }, token2);

        task.Start();
        task2.Start();

        Thread.Sleep(1000);
        cancelTokenSource.Cancel();
        Thread.Sleep(50); // без этого не успевает обновиться статус у меня..
        Console.WriteLine($"Task Status 1: {task.Status}");
        Thread.Sleep(1000);
        cancelTokenSource2.Cancel();
        Thread.Sleep(200); // можете закаментить для сравнения, и поиграть со значениями..
        Console.WriteLine($"Task Status 2: {task2.Status}");
        cancelTokenSource.Dispose();
        cancelTokenSource2.Dispose();
    }
}
в дотнет 8 ... не сразу... но
может устаревшие методы? но компилируются.. топят в пользу асинхронки?
... вот пример для 8
https://learn.microsoft.com/ru-ru/dotnet/api/syste...
обратите внимание на использование CancellationToken, он действительно обрывает исполнение до начала. можно конечно попытаться адаптировать под ваш случай.. но это если опять будет сильно не чего делать.. не отписывайтесь )))
using System.Runtime.CompilerServices;

public static class cancelTokenTest8and2task
{
    public static void print(this string s) => Console.WriteLine(s);
    public static void Main()
    {
        var ts1 = new CancellationTokenSource();
        var tk1 = ts1.Token;

        var ts2 = new CancellationTokenSource();
        var tk2 = ts2.Token;


        var t1 = new Task(() => {
            for (int i = 1; i < 100000; i++)
            {
                if (tk1.IsCancellationRequested)
                {
                    "Операция t1 прервана".print();
                    return;
                }
                $"t1. Квадрат числа {i} равен {i * i}".print();
                Thread.Sleep(200);
            }
        });

        var t2 = new Task(() => {
            for (int i = 1; i < 100000; i++)
            {
                if (tk2.IsCancellationRequested)
                {
                    "Операция t2 прервана".print();
                    return;
                }
                $"t2. Корень числа {i} равен {Math.Sqrt(i)}".print();
                Thread.Sleep(200);
            }
        });

        t1.Start();
        t2.Start();
        Thread.Sleep(1000);
        ts1.Cancel();
        Thread.Sleep(50);
        $"\nTask1 status: {t1.Status}".print();

        Thread.Sleep(1000);
        ts2.Cancel();
        Thread.Sleep(200);
        $"\nTask2 status: {t2.Status}".print();
        ts1.Dispose();
        ts2.Dispose();
    }
}
работает в дот нет 8 ... кроме using System.Runtime.CompilerServices; я разницы пока не вижу (имена не в счет!)...
Ответ написан
Комментировать
@mvv-rus
Настоящий админ AD и ненастоящий программист
CancellationToken, передаваемый коструктору Task, не имеет никакого отношения к делегату, составляющему тело задачи (у вас им является ваша лямбда-функция). Он служит для отмены самой задачи, пока она ещё не запущена на выполнение. А делегат тела задачи даже не может просто взять и обратиться к этому CancellationToken. И, чтобы делегат мог с ним работать, этот CancellationToken надо передать делегату каким-то другим образом.
Вы передаете этот CancellationToken через замыкание лямбда-функции (это умное слово означает, что вы просто сослались в ней на эту переменную). Причем, вы его передаете в тело и той, и другой задачи, через одну и ту же переменную token. Именно поэтому при вызове Cancel() для источника этого CancellationToken (cancelTokenSource) обе задачи обнаруживают этот факт и прекращают свое выполнение.
PS Если интересует, где про это прочитать побольше, то - в книге Дж.Рихтера "Программирование на платформе Microsoft .NET Framework 4.5 на языке Csharp, CLR via Csharp" в гл.27 (в более новом издании, если такое есть, номер главы может измениться).
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы