4

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

Я просто хочу узнать правильно ли я все понял и задать походу пару вопросов.

  1. Начнем с async. Метод помеченный async разрешает нам внутри данного метода использовать await и требует что бы возвращаемый тип имел метод GetAwaiter.
  2. Ключевое слово await. Await ставится перед операцией которая выполняется во вторичном потоке. Он позволяет не дожидаться пока вторичный поток (IO или обычный Thread) закончит свое выполнение. Await просто оборачивает то, что идет после await в отдельный метод, который по callback вызывается когда отработает вторичный поток. Тем самым первичный поток продолжает выполняться дальше. А когда отработает вторичный поток из пула потоков возьмется рандомный поток который довыполняет вторую часть метода.

В итого что это дает в приложениях

  • в приложениях с ui потоками(не до конца понятно что за ui поток, работаю исключительно asp.net) наш поток продолжит выполнение и не заблочит действия пользователя;
  • в asp.net mvc у меня первичный поток освободится и будет работать только вторичный;
  • и последнее синтаксический сахар который скрывает всю логику которая это делает.

Вопросы

  1. Правильно ли я все описал, действительно ли в этом смысл async await(разбить метод на две части и до выполнять вторую часть как callback, тем самым не задерживая первичный поток)?
  2. Что за метод configureawait?
  3. Почему многие говорят что эти ключевые слова не связаны с потоками. Ну как же await то ставится перед операцией, которая будет выполняться во вторичном потоке, да и суть то в чем, это конструкция была придумана что бы не держать первичный поток. Так что связь тут самая прямая или я не прав?
  4. Когда закончится выполняться вторичный поток, какой поток продолжит выполнять вторую часть метода. Тот самый вторичный поток? Или будет взят любой поток из thread pool?
Grundy
  • 81,538
Snuppi
  • 511
  • 1
    UI - это User Interface, т.е. визуальный интерфейс программы (окна, кнопки, чекбоксы и проч.). Поток UI - это тот поток, который "обслуживает" пользовательский интерфейс программы. Это конечно же имеет отношение к дестопным приложениям. – Bulson Mar 15 '18 at 16:28
  • 1
    @Bulson, стоит добавить, что это относится в десктопным приложениям – Grundy Mar 15 '18 at 16:28

1 Answers1

5

Для начала, ваши предположения.

  1. Ну как бы технически говоря да. Но есть ещё одна важная для понимания вещь: с ключевым словом async функция разбиваются на куски так, что между этими кусками функция вообще нигде не бежит. А каждый из кусков выполняется синхронно, как обыкновенная функция. Границы этих кусков идут по await.

  2. «Await ставится перед операцией которая выполняется во вторичном потоке.» — это неверно, await означает лишь, что в этой точке функция прекращает выполнение, и возобновляется тогда, когда Task, по отношению к которому производится await, завершается.

    Task, как мы уже видели, вовсе не означает «кусок кода, бегущий в каком-то потоке». Например, async-функция производит Task, который бежит в конкретном потоке лишь иногда, кусками.


Теперь, ваши вопросы.

  1. В общем и целом правда, но не на одну часть, а на много. Представьте себе, например, такой код:

    async Task PrintSlowly(IEnumerable<int> data)
    {
        Console.WriteLine("begin");
        foreach (var n in data)
        {
            await Task.Delay(1000);
            Console.WriteLine(data);
        }
        Console.WriteLine("end");
    }
    

    В этом методе у нас k + 1 кусок, где k — количество элементов, полученных из IEnumerable<int>.

  2. Если await выполняется в UI-потоке (или в любом потоке, в котором есть свой TaskScheduler/SynchronizationContext), то следующий кусок функции перебрасывается для выполнения в тот же поток/SynchronizationContext. Если же вам это не нужно, вы можете сказать ConfigureAwait(false), что означает «мне безразлично, в каком контексте выполнятся остальные куски async-функции». При этом рантайм может заставить их выполняться где угодно, гарантий нет.

  3. Нет, операция, на которую делается await, не обязательно будет выполняться в другом потоке. Task не есть «кусок кода, который выполняется где-то ещё». Если эта операция реально не блокирует текущий поток, то да, текущий поток будет свободен. Но вы легко можете написать Task, который будет блокировать текущий поток:

    async Task Weird()
    {
        if (Math.Sin(0) == 42)
            await Task.Delay(1000);
        for (int x = 0; x < 1_000_000; x++)
        for (int y = 0; y < 1_000_000; y++)
        for (int z = 0; z < 1_000_000; z++)
        {
            if (x * x * x * x + y * y * y * y == z * z * z * z)
                throw new Exception("Ферма лох");
        }
    }
    
  4. Где и как выполняется Task, определяется исключительно самим Task'ом. Единственный случай, когда Task целиком выполняется на побочном потоке, есть Task, запущенный через Task.Run() (в этом случае поток берётся из пула потоков). В остальных случаях забота о том, где именно выполняться, лежит на самом Task'e, async/await этим не занимается, и даже не требует, чтобы Task вообще где-то выполнялся. Task должен бы завершаться, это да (и то не обязан), а выполняться он может вообще нигде. Вот пример:

    static Task WaitTimer()
    {
        var tcs = new TaskCompletionSource<int>();
        new System.Threading.Timer(o => tcs.SetResult(0), null, 3000,
                                   System.Threading.Timeout.Infinite);
        return tcs.Task;
    }
    

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

VladD
  • 206,799
  • 4
    а почему бы не хлопнуть как дубликат? :-) так вроде уже все расписано? :-) – Grundy Mar 15 '18 at 16:37
  • 1
    @Grundy: Там вопрос совершенно не об этом. А то, что там в ответе есть хорошее описание работы async/await, ну так оно ж не отвечает на вопросы ТС прямо. – VladD Mar 15 '18 at 16:42
  • 4
    Ну вот :-D а мы потом удивляемся почему у кроликов такие большие репутации :-D – Grundy Mar 15 '18 at 16:57
  • 1
    @Grundy: Ой, и не говорите! – VladD Mar 15 '18 at 17:03
  • 1
    не совсем понял это предложение: Поток, который выполняет код, не может завершиться, так что проблем нет. почему поток не может завершиться? – Grundy Mar 15 '18 at 17:09
  • 1
    @Grundy: Он выполняет код. Он не может завершиться посередине выпонения этого кода, «сам по себе». – VladD Mar 15 '18 at 17:12
  • 1
    @Grundy: Хм, кажется, я плохо прочитал вопрос. Сейчас отредактирую. – VladD Mar 15 '18 at 17:13
  • 1
    @VladD дублика же, вызванный тем, что топикастер думает что таск - это поток, а асинхронность - это многопоточность –  Mar 15 '18 at 18:53
  • 1
    @PashaPash: У ТС конкретные вопросы, на которые в том ответе есть информация, но прямого ответа нет. Ну так информация есть и в MSDN, ТС говорит, что не понимает. – VladD Mar 15 '18 at 20:31
  • @PashaPash: "дублика же, вызванный тем, что топикастер думает что таск - это поток, а асинхронность - это многопоточность" А что асинхронность это не вторичный поток? Есть синхронное выполнение методов и асинхронное т.е во вторичном потоке. Разве Task это не оболочка над thread. Раньше потоки называли потоками а сейчас тасками. Таска представляет из себя нейкое описание процесса выполняющегося в потоке. – Snuppi Mar 16 '18 at 08:25
  • 1
    @Snuppi: Нет, Таск — абстракция более высокого уровня, и не ограничивается кусками кода, бежащими в каком-то потоке. Последний кусок кода из ответа как раз контрпример к вашему утверждению. – VladD Mar 16 '18 at 08:30
  • 1
    @VladD проблема в том, что под "абстракция более высокого уровня" многие воспринимают "абстракция более высокого уровня надо потоками" –  Mar 16 '18 at 09:18
  • 1
    @Snuppi нет, Task это не оболочка над Tread. В случае многопоточности в asp.net - это почти всегда не оболочка над Tread. Например, длинный запрос в SQL Server - это Task. При этом он не требует нового потока на той машине, с которой он был сделан. Т.е. вам не надо активно выполнять никакой код, чтобы ждать окончания запроса. В синхронном варианте - поток, из которого сделали запрос, просто спит и ждет. В асинхронном - поток освобождается, а по завершению запроса - обработка выполнения результатов авто-запускается в потоке с тем же http context. –  Mar 16 '18 at 09:26
  • 1
    @Snuppi т.е. суть async/await в asp.net - это наоборот, не запускать дополнительные потоки вообще + освобождать потоки, выделенные на время обработки запроса (те, в которых есть httpcontext) на время долгих сетевых операций. в WinForms / WPF, соответственно, это способ "отпускать" UI поток на время ожидания фоновых задач, но при этом выполнять код и до и после слова await в одном потоке. –  Mar 16 '18 at 09:31
  • 1
    @Snuppi Т.е. вместо "в asp.net mvc у меня первичный поток освободится и будет работать только вторичный;" - "в asp.net mvc у меня первичный поток освободится и ни один поток работать не будет, пока не завершится какая-то длительная IO операция". "когда отработает вторичный поток из пула потоков возьмется рандомный поток который довыполняет вторую часть метода." -> "когда завершится операция -> остаток метода будет довыполнен в том же httpcontext-е, что и первая часть.". –  Mar 16 '18 at 09:35
  • Фууух что ж более менее понял, спасибо огромное за объяснение) Всем спасибо) – Snuppi Mar 16 '18 at 09:53