CancellationTokenSource.Cancel () зависает - PullRequest
0 голосов
/ 21 сентября 2018

Я наблюдаю зависание CancellationTokenSource.Cancel, когда один из асинхронных каналов находится в активном цикле.

Полный код:

static async Task doStuff(CancellationToken token)
{
    try
    {
        // await Task.Yield();
        await Task.Delay(-1, token);
    }
    catch (TaskCanceledException)
    {
    }

    while (true) ;
}

static void Main(string[] args)
{
    var main = Task.Run(() =>
    {
        using (var csource = new CancellationTokenSource())
        {
            var task = doStuff(csource.Token);
            Console.WriteLine("Spawned");
            csource.Cancel();
            Console.WriteLine("Cancelled");
        }
    });
    main.GetAwaiter().GetResult();
}

Печатает Spawned и зависает.Callstack выглядит следующим образом:

ConsoleApp9.exe!ConsoleApp9.Program.doStuff(System.Threading.CancellationToken token) Line 23   C#
[Resuming Async Method] 
[External Code] 
ConsoleApp9.exe!ConsoleApp9.Program.Main.AnonymousMethod__1_0() Line 34 C#
[External Code] 

Отмена записи await Task.Yield приведет к выводу Spawned\nCancelled.

Есть идеи, почему?Гарантирует ли C #, что некодированная асинхронность никогда не будет блокировать другие асинхронности?

1 Ответ

0 голосов
/ 22 сентября 2018

CancellationTokenSource не имеет никакого понятия о планировщике задач.Если обратный вызов не был зарегистрирован в пользовательском контексте синхронизации, CancellationTokenSource выполнит его в том же стеке вызовов, что и .Cancel().В вашем случае обратный вызов отмены завершает задачу, возвращаемую Task.Delay, затем продолжение встраивается, что приводит к бесконечному циклу внутри CancellationTokenSource.Cancel.

Ваш пример с Task.Yield работает только из-засостояние гонки.Когда токен отменен, поток не начал выполнять Task.Delay, поэтому нет продолжения для inline.Если вы измените свой Main, чтобы добавить паузу, вы увидите, что он по-прежнему будет зависать даже с Task.Yield:

static void Main(string[] args)
{
    var main = Task.Run(() =>
    {
        using (var csource = new CancellationTokenSource())
        {
            var task = doStuff(csource.Token);
            Console.WriteLine("Spawned");

            Thread.Sleep(1000); // Give enough time to reach Task.Delay

            csource.Cancel();
            Console.WriteLine("Cancelled");
        }
    });
    main.GetAwaiter().GetResult();
}

Прямо сейчас, единственным надежным способом защитить вызов CancellationTokenSource.Cancel - это завернуть в Task.Run.

...