Задача C # - связанный токен отмены не работает

Пожалуйста, если кто-нибудь может мне помочь. Я пытаюсь использовать токены отмены, связанные с TPL. Проблема заключается в том, что после отмены основного CancellationTokenSource значение свойства IsCancellationRequested связанного токена по-прежнему равно «false».

Я начинаю две задачи, на всякий случай - но это должно быть одно и то же. Первому передаю CancellationToken, а второму - CancellationTokenSource. Поведение такое же: в циклах while - условие connectedToken.IsCancellationRequested остается «ложным» после отмены.

Вот код, который я использую:

public class Manager
{
    private Task tokenTask;
    private Task sourceTask;
    private CancellationTokenSource mainCancelationTokenSource;
    private CancellationToken mainToken;

    public Manager()
    {
        this.mainCancelationTokenSource = new CancellationTokenSource();
        this.mainToken = mainCancelationTokenSource.Token;
        this.mainToken.Register(MainCanceled);
    }

    public void Start()
    {
        Workers w = new Workers();
        tokenTask = Task.Run(() => w.DoWorkToken(mainToken), mainToken);
        sourceTask = Task.Run(() => w.DoWorkSource(mainCancelationTokenSource), mainCancelationTokenSource.Token);

    }
    public void Cancel()
    {
        mainCancelationTokenSource.Cancel();
    }

    private void MainCanceled()
    {
        try
        {
            tokenTask.Wait();
        }
        catch (Exception e)
        {

        }

        try
        {
            sourceTask.Wait();
        }
        catch (Exception e)
        {

        }
    }
}

class Workers
{
    public void DoWorkToken(CancellationToken mainToken)
    {
        CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(mainToken);
        CancellationToken linkedToken = linkedCts.Token;

        while (!linkedToken.IsCancellationRequested)
        {
            Random r = new Random();
            Task.Delay(200 * r.Next(1, 11)).Wait();
        }

        linkedToken.ThrowIfCancellationRequested();
    }

    public void DoWorkSource(CancellationTokenSource mainCts)
    {
        CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(mainCts.Token);

        while (!linkedCts.Token.IsCancellationRequested)
        {
            Random r = new Random();
            Task.Delay(200 * r.Next(1, 11)).Wait();
        }

        linkedCts.Token.ThrowIfCancellationRequested();
    }
}

Чтобы запустить этот код из основного метода консольного приложения:

class Program
{
    static void Main(string[] args)
    {
            Manager manager = new Manager();
            manager.Start();
            //Console.ReadKey();

            Thread.Sleep(5000);
            manager.Cancel();
   }
}

Спасибо за помощь!


person Thomas    schedule 10.11.2016    source источник


Ответы (1)


Источником этой проблемы является эта строка:

this.mainToken.Register(MainCanceled);

Вы регистрируете обратный вызов для выполнения при отмене токена. Внутренне этот обратный вызов делегируется родительскому CancellationTokenSource и помещается в некоторый список обратных вызовов для выполнения, когда запрашивается отмена источника. В этом обработчике вы делаете

tokenTask.Wait();
sourceTask.Wait();

Таким образом, этот обратный вызов не будет выполнен, пока не будут выполнены связанные задачи. В задачах вы это делаете

while (!linkedToken.IsCancellationRequested) {
    // loop here
}

Таким образом, задачи не будут выполнены, пока не поступит запрос на отмену.

Теперь перейдем к сложной части: когда вы создаете связанный источник токена

CancellationTokenSource.CreateLinkedTokenSource(mainToken)

Он также помещает обратный вызов в список родительских (mainToken) источников токенов. Когда этот обратный вызов выполняется - источник связанного токена также отменяется. Этот обратный вызов будет после вашего обратного вызова в списке.

Итак, вы зашли в тупик, потому что первый обратный вызов (ваш) ожидает завершения задач, задачи ожидают, когда связанный токен IsCancellationRequested будет равен true, а связанный источник токена ожидает завершения своего собственного обратного вызова, чтобы установить этот флаг.

Чтобы решить эту проблему, просто удалите обратный вызов или напишите его таким образом, чтобы он не блокировал ожидание завершения связанной задачи, или измените задачи так, чтобы они не ожидали флага IsCancellationRequested источника токена связанного.

person Evk    schedule 10.11.2016
comment
Спасибо за ваш ответ. Когда убираю обратный вызов, все работает. Но для ясности, я изменил условие в циклах while на: while (!mainToken.IsCancellationRequested). (Это токен из источника, который отменен - ​​в другом цикле while я поставил условие с источником соответственно). И я добавил обратный вызов. Этот токен отменяется, задачи завершаются, Wait () в обратном вызове завершается, и все работает. Могу ли я сделать вывод, что связанный источник помещен в этот список обратных вызовов, но сам источник отменяется, несмотря на зарегистрированный обратный вызов? - person Thomas; 10.11.2016
comment
@Thomas, вы правы, и я был частично неправ, я обновил свой ответ. - person Evk; 10.11.2016