Все время будет печать 1 2 3(по строчно). Я слегка офигел, т.к. был в полной уверенности, что код
await ProcessTaskAsync();
Console.WriteLine(3);
неблокирующий. Т.е. создаем поток для таски, там ее выполняем, цеплякм continuation
Console.WriteLine(3)
и возврашаемся тут же в цикл. Ожидал вот это 11111111111111....1231111111111111111111111111111...23......
А фигушки, оказывается, что продолжение цикла это тоже ведь continuation, поэтому данный код у нас блокирующий. Очень интуитивное поведение
Я ожидал, что будет что-то вроде
while (true)
{
Console.WriteLine(1);
var tsk = ProcessTaskAsync()
tsk.ContinueWith(t=> Console.WriteLine(3);)
}
Т.е. неблокирующий сервер таким образом через async\await не напишешь. Век живи, век учись.
Кодом людям нужно помогать!
Re: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sharov, Вы писали:
S>Здравтсвуйте.
S>Наверняка подобный вопрос(задачку) здесь разобрали лет 5-6 назад, если не ранее. Я как всегда вовремя
S>С удивлением узнал как работает сл. код: S>
S>неблокирующий. Т.е. создаем поток для таски, там ее выполняем, цеплякм continuation S>
S>Console.WriteLine(3)
S>
S> и возврашаемся тут же в цикл. Ожидал вот это 11111111111111....1231111111111111111111111111111...23......
S>А фигушки, оказывается, что продолжение цикла это тоже ведь continuation, поэтому данный код у нас блокирующий. Очень интуитивное поведение S>Я ожидал, что будет что-то вроде S>
Здравствуйте, Qulac, Вы писали:
Q>А он вообще нужен здесь await?
Ну да, Console.WriteLine(3); должна выполняться сразу после, они могут быть как-то связаны -- освобождение ресурсов и т.д.
Я выше описал на тасках как думал, что этот код будет работать. А оно эвана как...
Кодом людям нужно помогать!
Re: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sharov, Вы писали:
S>Здравтсвуйте.
S>Наверняка подобный вопрос(задачку) здесь разобрали лет 5-6 назад, если не ранее. Я как всегда вовремя
S>С удивлением узнал как работает сл. код: S>
S>неблокирующий. Т.е. создаем поток для таски, там ее выполняем, цеплякм continuation S>
S>Console.WriteLine(3)
S>
S> и возврашаемся тут же в цикл. Ожидал вот это 11111111111111....1231111111111111111111111111111...23......
Как раз нет. Ты же сам сказал — подождать (await). Весь смысл такого кода — возможность писать кооперативную многозадачность.
Т.е. твой код выглядит (и работает) как синхронный однопоточный; но при этом он не замораживает физический поток на время ProcessTaskAsync(). В пуле потоков не болтаются спящие потоки, тормозя шедулер; в адресном пространстве процесса не сожрано по 1 мегабайту на стек каждой такой "спящей красавицы", всё такое. Нет затрат на переключение контекстов в момент когда ты делаешь await — usermode шедулер, который исполняет твой Test(), берёт следующий Task, готовый к выполнению, и шарашит его.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sinclair, Вы писали:
S>Как раз нет. Ты же сам сказал — подождать (await). Весь смысл такого кода — возможность писать кооперативную многозадачность.
Я всегда стараюсь явно писать все через таски, поэтому async\await старался избегать. Мне казалось, что суть await в данном месте это подождать в отдельном,уже созданном для ProcessTask, потоке результата операции
и продолжить его с кодом после await. Причем все это в потоке отдельном от потока while(true). Ровно так, как я описал через task.
S>В пуле потоков не болтаются спящие потоки, тормозя шедулер;
А как они его тормозят? Ну спят и спят, возьмет не спящий. А вот если таких нет, то придется создавать.
Кодом людям нужно помогать!
Re[3]: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sharov, Вы писали:
S>А как они его тормозят? Ну спят и спят, возьмет не спящий. А вот если таких нет, то придется создавать.
При каждом переключении, шедулер ОС принимает решение о том, кому отдать квант времени.
Разработчики ядер много работают, чтобы затраты на принятие такого решения были минимальны и не катастрофически зависели от количества имеющихся потоков.
Тем не менее, чудес и алгоритмов за O(1) не бывает. Неактивные потоки — ждущие на примитивах синхронизации или на Sleep() — занимают место в списке потоков, и замедляют переключение.
Где-то была в своё время статья про то, сколько ресурсов тратится на собственно "шедулинг"; для любого процессора и любого 0<e<100% существует конечное N такое, что при регистрации N спящих потоков в системе на работу единственного неспящего будет выделяться не более e мощности процессора.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sinclair, Вы писали:
S>>А как они его тормозят? Ну спят и спят, возьмет не спящий. А вот если таких нет, то придется создавать. S>При каждом переключении, шедулер ОС принимает решение о том, кому отдать квант времени. S>Разработчики ядер много работают, чтобы затраты на принятие такого решения были минимальны и не катастрофически зависели от количества имеющихся потоков. S>Тем не менее, чудес и алгоритмов за O(1) не бывает. Неактивные потоки — ждущие на примитивах синхронизации или на Sleep() — занимают место в списке потоков, и замедляют переключение.
Я так и думал, просто мне казалось что расход на сотни, край тысячи таких потоков по нашим дням дело плевое. Расход в смысле O(n). А так да, согласен.
Кодом людям нужно помогать!
Re[5]: Блокирующий вызов, базовый вопрос по async\await
непонятно что хотел автор получить...
выглядит как шумоподобное бессмсленное нагромождение асинков таков авэйтов
тем не менее код работает ожидаемо никаких неправильностей не вижу
ввод автора что аинхронный сервер написать на асинхронных и авэйтах нельз конечно ошибочен
и главное вовсе не следует из кода что автор привел!
Из авторского кода наоборот следует что все работает как надо и пригодно к задаче.
Здравствуйте, paradoks, Вы писали:
P>тем не менее код работает ожидаемо никаких неправильностей не вижу
Для меня было неожиданностью, предполагал, что код будет неблокирующим. Ожидал, что каждый запрос(итерация цикла) будет неблокирующая в отдельном потоке.
Судя по всему потоки действительно будут разные, но будет работать как блокирующие, т.е. не будет параллельной обработки.
P>ввод автора что аинхронный сервер написать на асинхронных и авэйтах нельз конечно ошибочен
Такого вывода, скорее это был вопрос без вопросительного знака.
Кодом людям нужно помогать!
Re[2]: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sinclair, Вы писали:
S>Как раз нет. Ты же сам сказал — подождать (await). Весь смысл такого кода — возможность писать кооперативную многозадачность.
Да, но я предполагал, что семантика будет а-ля tsk.ContinueWith. Отдельно от потока цилка. Тут главное понять было, что
сл. итерации цикла это ведь тоже continuation, наряду с Console.WriteLine(3).
Кодом людям нужно помогать!
Re[3]: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sharov, Вы писали:
S>Мне казалось, что суть await в данном месте это подождать в отдельном, уже созданном для ProcessTask, потоке результата операции S>и продолжить его с кодом после await.
Как-то так оно и работает. Ты вызываешь асинхронный Test, он выполняется до первого await и возвращает управление. В результате "главный" поток печатает
1
La fin
и переходит дальше на ReadLine, где и останавливается в ожидании ввода. А то, что мы авейтим и всё остальное после него, крутится в это время в каком-то другом потоке/потоках. Поскольку приложение консольное и контекст синхронизации у главного потока не задан (его SynchronizationContext.Current is null), то вот это всё остальное после await в главный поток больше не вернётся, т.к. без контекста синхронизации не может.
В WinForms/WPF-приложении результат, думаю, будет сильно другой. Там главным потокам задан SynchronizationContext.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
private static void Main(string[] args)
{
Test();
Print("La fin");
Console.ReadLine();
}
static async void Test()
{
while (true)
{
Print(1);
await ProcessTaskAsync();
Print(3);
}
}
static Task ProcessTaskAsync()
{
return Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Print(2);
});
}
static void Print(object message)
{
Console.WriteLine($"thread {Thread.CurrentThread.ManagedThreadId} says {message}");
}
}
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, paradoks, Вы писали:
P>>тем не менее код работает ожидаемо никаких неправильностей не вижу
S>Для меня было неожиданностью, предполагал, что код будет неблокирующим. Ожидал, что каждый запрос(итерация цикла) будет неблокирующая в отдельном потоке.
но откуда такие ожидания взялись? тем более что ты САМ поставил внутри цикла БЛОК — await
Убери его, сделай все вызовы внутри блока асинхронными и цикл у тебя будет не-блокирующий.
Как можно было ожидать что цикл не заблокируется если ты его САМ заблокировал?
Ты же понимаешь что твои ожидания взялись ниоткуда и не основаны на том коде что ты напиcал?
имхо если у тебя задача обработки асинхронных входных сообщений асинхронныи функциями — то все это отлично работает
Re[8]: Блокирующий вызов, базовый вопрос по async\await
P>Как можно было ожидать что цикл не заблокируется если ты его САМ заблокировал?
Почему await==блокировке? Разве это не разделение между таском и его продолжением, синтаксический захар над
tsk = ProcessAsync(...);
tsk.ContinueWith(...)
?
P>Ты же понимаешь что твои ожидания взялись ниоткуда и не основаны на том коде что ты напиcал?
Ожидания взялись от неправильного понимания работы await. Он, видимо, возвращает управление в вызвавший его поток.
Ну т.е. разница между tsk.ContinueWith и await весьма огромна.
P>имхо если у тебя задача обработки асинхронных входных сообщений асинхронныи функциями — то все это отлично работает
Код выше синхронный, в том то и дело. Пока ProcessAsync не закончится, цикл не продолжится.
Кодом людям нужно помогать!
Re[9]: Блокирующий вызов, базовый вопрос по async\await
S>Код выше синхронный, в том то и дело. Пока ProcessAsync не закончится, цикл не продолжится.
а в чем смысл этого?
зачем тебе ждать СИНХРОННО когда вывод на экран закончится? и при этом назвать ф-и мнемонически асинхронной...
неблокирующий асинхронный сервер что ты упоминал как раз и не должен лочиться на таких подзадачах
Здравствуйте, Sharov, Вы писали:
S>Не совсем то, должно быть(точнее, мое ожидание см. выше) 23 и неблокирующее, т.е. 11111...11123111..11123111....23 и т.д. S>А тут 132132132.
while (true)
{
Console.WriteLine(1);
ProcessTaskAsync();
Console.WriteLine(3);
}
а так? получаем 131313131313132...
Re[4]: Блокирующий вызов, базовый вопрос по async\await
Тут у меня проблемы и вопроса как такового нет. Суть не в том, что мы получаем, суть в том, что мои ожидания(неблокирующая обработка) не совпала с реальностью.
Кодом людям нужно помогать!
Re[5]: Блокирующий вызов, базовый вопрос по async\await
Здравствуйте, Sharov, Вы писали:
S>Тут у меня проблемы и вопроса как такового нет. Суть не в том, что мы получаем, суть в том, что мои ожидания(неблокирующая обработка) не совпала с реальностью.
await — это не неблокирующая обработка, это неблокирующее ожидание. Но именно — ожидание.
"Обычное" блокирующее ожидание это два живых потока, один ждет другого.
Async/await же ожидание делается путем модификации кода так, что запускается обработка в некотором потоке, а код, который идет после await, исполняется после окончания выполнения асинхронного кода (обычно в том же потоке где крутился асинхронный код, хотя необязательно). А до того, как асинхронный код выполнится — никакого ожидающего кода попросту нет. Это только Таsk, который запланирован запуститься когда-то, когда закончится тот асинхронный код. Как обработчик события при асинхронном методе.
Если в "обычном" блокирующем ожидании в первом потоке есть код "до ожидания" и "код после ожидания", то в случае async/await код метода компилируется иначе — в первом потоке остается только код "до ожидания", а "код после ожидания" запускается отдельно после собственно окончания ожидания.
Грубо говоря (без погружения в детали работы шедулеров) этот код будет обрабатываться так:
1. Выполнить в текущем потоке Р1
2. Запланировать выполнение задания для P2 в отдельном потоке, а в текущем потоке ЗАКОНЧИТЬ ВЫПОЛНЕНИЕ ЭТОГО МЕТОДА (включая выход из цикла, ага) и вернуться в вызвавший код (в твоем случае это будет в первый раз Main где придем на ReadLine, а в последующих см. дальше). (*)
3. Когда Р2 закончится — в том же потоке что работал P2 продолжить выполнение P3. А это цикл и повторение пунктов 1-3, где в случае 2 уже переходим на вот этот же поток. И кстати да, это прямой аналог ContinueWith — продолжить в том же потоке выполнение следующего Task (при базовых настройках шедулера и все равно на его усмотрение — может быть создан и отдельный поток, но "обычно и по умолчанию" используется текущий).
Т.е. схема по потокам грубо будет выглядеть так:
T1: P1->done
T2: P2->P3->done
(*)При этом создание и постановка ее в планирование задачи идет первым делом и если текущий поток на этом заканчивается (как это будет во всех твоих итерациях цикла после первой), то может получиться и так, что для выполнения P2 будет он же и использован. Может он же может не он же, как повезет.
Если приложение дополнить выводом ид потока, то оно будет наглядно видно:
Обрати внимание на первые три строки — главный поток не блокируется на первом же await. И даже не ждет его — код, запланированный после await выполняется уже в другом потоке после окончания первого await.