Почему этот асинхронный код не продолжает работать в том же контексте, в котором он ожидался? - PullRequest
0 голосов
/ 04 апреля 2019

Я пытаюсь понять, как работает Task.ContinueWith . Рассмотрим следующий код:

private async void HandleButtonClick(object sender, EventArgs e)
{
    Console.WriteLine($"HandleButtonClick: a {GetTrdLabel()}");
    var t1 = Task.Run(() => DoSomethingAsync("First time"));
    Console.WriteLine($"HandleButtonClick: b {GetTrdLabel()}");
    await t1;
    Console.WriteLine($"HandleButtonClick: c {GetTrdLabel()}");

    var t2 = t1.ContinueWith(async (t) =>
    {
        Console.WriteLine($"t3: a {GetTrdLabel()}");
        Thread.Sleep(2000);
        Console.WriteLine($"t3: b {GetTrdLabel()}");
        await DoSomethingAsync("Second time");
        Console.WriteLine($"t3: c {GetTrdLabel()}");
    });

    Console.WriteLine($"HandleButtonClick: d {GetTrdLabel()}");
    await t2;
    Console.WriteLine($"HandleButtonClick: e {GetTrdLabel()}");
}

private async Task DoSomethingAsync(string label)
{
    Console.WriteLine($"DoSomethingElseAsync ({label}): a {GetTrdLabel()}");
    Thread.Sleep(2000);
    Console.WriteLine($"DoSomethingElseAsync ({label}): b {GetTrdLabel()}");
    await Task.Delay(2000);
    Console.WriteLine($"DoSomethingElseAsync ({label}): c {GetTrdLabel()}");
}

private string GetTrdLabel() => $"({Thread.CurrentThread.ManagedThreadId})";

Выход ниже. У меня вопрос о выделенных строках: почему первый не продолжается в захваченном контексте после await - т.е. идентификатор управляемого потока 3 - поскольку я не использовал .ConfigureAwait(false)? Второй - это , продолжающийся, как и ожидалось, то есть идентификатор потока 4.

Мне кажется, что это как-то связано с "... попыткой упорядочить продолжение обратно в исходный захваченный контекст" (выделено мной) из документации, но я не понимаю, почему попытка не удалась в первом случае.

enter image description here

Ответы [ 2 ]

2 голосов
/ 05 апреля 2019

пытается понять, как работает Task.ContinueWith

Лучше просто игнорировать ContinueWith и использовать вместо него await.Но, если вы хотите изучить низкоуровневый, опасный способ сделать что-то, то я обязуюсь.Пожалуйста, не используйте в производстве.

Первое, на что нужно обратить внимание, это то, что ContinueWith всегда планирует работу в планировщике задач.И он не использует планировщик задач по умолчанию;по умолчанию используется планировщик задач current .Предполагая, что HandleButtonClick вызывается непосредственно вашей структурой пользовательского интерфейса (а не, например, запланировано с помощью планировщика задач), тогда нет текущего планировщика задач, поэтому ContinueWith будет использовать планировщик задач по умолчанию, который является задачей пула потоковпланировщик.Чтобы избежать такого рода запутанных рассуждений, код должен всегда передавать TaskScheduler в ContinueWith.

Следующее, что следует отметить, это то, что ContinueWith не понимает async делегатов,Что касается этого, async лямбда - это просто причудливый способ дать ему Func<Task> делегат, а ContinueWith заботится только о начальной синхронной части этого метода.

Последнее, что нужно сделатьобратите внимание, что потоки пула потоков считаются взаимозаменяемыми.Это верно для любого async / await кода;если он работает в контексте пула потоков и выполняет await, он может возобновить выполнение в любом потоке пула потоков.Это может быть или не быть тем потоком, в котором выполнялся код до await.

0 голосов
/ 04 апреля 2019

Если вы попробуете один и тот же код в приложении Windows, вы получите другие результаты.Поток пользовательского интерфейса приложения Windows работает в другом контексте, и элемент управления возвращается в поток пользовательского интерфейса после ожидаемой операции.Это не относится к консольным приложениям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...