Здравствуйте, another_coder, Вы писали:
_>Я читал о том, что подобный код нормально работать не будет
Потому что await захватывает текущее состояние (упрощая, значение локальных переменных) и не блокирует поток, а передаёт его следующей в очереди задаче.
Лок так и останется захваченным, пока await не завершится, и код не продолжит работу с того места, где прервался.
Проблема раз: продолжить работу код может в любом потоке (и для orleans — на любой машине), не обязательно в том, в котором захватил лок.
Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.
Правильное — не использовать блокировки, они в async-коде в 99% случаев нафиг не нужны. Вместо этого достаточно собрать все параллельно выполняющиеся ветки в Task.WhenAll(), дождаться завершения + продолжить выполнение в гарантированно чистом состоянии без гонок.
Не, в отдельных случаях может оказаться удобнее асинхронная очередь или любой другой producer-consumer, но это именно исключения.
Re: Почему не следует смешивать async-await и lock?
Здравствуйте, another_coder, Вы писали:
_>Я читал о том, что подобный код нормально работать не будет _>Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её?
Здравствуйте, xy012111, Вы писали:
X>Здравствуйте, another_coder, Вы писали:
_>>Я читал о том, что подобный код нормально работать не будет _>>Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её?
X>А погуглить lock async c# пробовали?
сноска про удачную линку с понятным объяснением возникла после гугла
Здравствуйте, Sinix, Вы писали:
S>Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.
Я правильно понимаю, что в этом случае возникнет deadlock?
Re[3]: Почему не следует смешивать async-await и lock?
S>>Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.
_>Я правильно понимаю, что в этом случае возникнет deadlock?
Неа, картина такая
// Задача1lock(Smth)
{
// освободили поток, лок не освобождён, другая задача выполняетlock(Smth)
{
// лок захвачен повторно из-за reentrancy.
}
}
Re[3]: Почему не следует смешивать async-await и lock?
Здравствуйте, another_coder, Вы писали:
_>>>Я читал о том, что подобный код нормально работать не будет _>>>Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её? X>>А погуглить lock async c# пробовали?
_>сноска про удачную линку с понятным объяснением возникла после гугла
То есть, вы сами из довольно большого списка объяснений не смогли выбрать удовлетворившее вас и понадеялись, что кто-то "наугад" (не знаю, что для вас является "удачным") поможетт?
Может, удачнее будет выбрать какую-то более понятную статью и попросить здесь уточнить не ясные вам, спорные на ваш взгляд конкретные моменты в этой статье?
Re[4]: Почему не следует смешивать async-await и lock?
X>Может, удачнее будет выбрать какую-то более понятную статью и попросить здесь уточнить не ясные вам, спорные на ваш взгляд конкретные моменты в этой статье?
А вот с удовольствием посмотрел бы на подробную статью по теме.
Только написанную человеком, который всю эту прелесть на практике разгребал. И с использованием готовых примитивов, а не с очередным велосипедом — самостроем.
А то как всегда: пока, скажем, на вот эти грабли не наступишь — не узнаешь.
Re[4]: Почему не следует смешивать async-await и lock?
Здравствуйте, Sinix, Вы писали:
_>>Я правильно понимаю, что в этом случае возникнет deadlock? S>Неа, картина такая S>
S>// Задача1
S>lock(Smth)
S>{
S> // освободили поток, лок не освобождён, другая задача выполняет
S> lock(Smth)
S> {
S> // лок захвачен повторно из-за reentrancy.
S> }
S>}
S>
Т.е. поблема может появиться, если мы рекурсивно вызываем методы, в которых есть lock одного и того же объекта. Я прав?
А в случае одновременного вызова метода Method в нескольких потоках не будет проблем?
Re: Почему не следует смешивать async-await и lock?
Эх, карате тут не поможет — надо было больше писать на Win32 API
Суть обсуждаемого явления проста как 3 копейки: ничего инновационного в мире Windows по сути не происходит со времён Protected Mode, и все плюшки и "прорывы" суть синтаксический сахар над уже порядком проржавевшей ходовой и чихающим движком.
Решение: чтобы UI не замерзал, надо дать ему возможность прокачивать очередь сообщений. На Delphi для этого был супер-метод Application.ProcessMessages, в визуал-бейсике тоже было что-то своё, и вот для C# спустя много лет был рождён async/await. Если почитать мануал про эту супер-фичу, то там вскользь говорится "на месте этих инструкций генерится служебная state-машина". Что это означает на самом деле? Банальный вызов старого-доброго PostMessage!
lock(lockObj)
{
foo();
await bar();
}
Брюки превращаются:
lock(lockObj)
{
foo();
bar_params_context = PACK_PARAMS(тут необходимые входные параметры для bar);
PostMessage(WM_ASYNC_AWAIT, &bar, bar_params_context);
}
void AsyncAwaitMachine(method, params_context) message WM_ASYNC_AWAIT // это обработчик оконной процедуры
{
if (method == &bar)
{
bar_params_context = UNPACK_PARAMS(params_context);
bar(bar_params_context);
}
else if
{
//тут другие async-await методы
}
}
Само собой, это сильно упрощённый псевдо-код, но суть явления не меняется: когда встречается await, это означает, что в очередь сообщений окна кладётся служебное сообщение. И продолжение исполнения кода случится ровно тогда, когда оконная процедура доберётся до этого служебного сообщения (исполнив все предыдущие, часть которых тоже может быть продолжением других async-await пар).
Казалось бы при чём тут lock — но авторы Delphi уже собрали эту граблю "пацаны, у меня прога виснет в дедлоке, а если закомменчу ProcessMessages, то всё работает, просто GUI не прорисовывается до конца обработки". Поэтому правило: залочил объект — не выходи из скоупа без его разлочки, иначе рано или поздно будет классический дедлок на двух последовательно залочиваемых примитивах "1,2" + "2,1"
Re[3]: Почему не следует смешивать async-await и lock?
Здравствуйте, another_coder, Вы писали:
_>Здравствуйте, Sinix, Вы писали:
S>>Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.
_>Я правильно понимаю, что в этом случае возникнет deadlock?
Нет, deadlock не возникнет, а вот race condtion и unexpexted behavior запросто.
Кодом людям нужно помогать!
Re[2]: Почему не следует смешивать async-await и lock?
Здравствуйте, Mr.Delphist, Вы писали:
MD>Само собой, это сильно упрощённый псевдо-код, но суть явления не меняется: когда встречается await, это означает, что в очередь сообщений окна кладётся служебное сообщение.
Товарищи веб-девелоперы смотрят на этот коммент с некоторым недоумением
MD>И продолжение исполнения кода случится ровно тогда, когда оконная процедура доберётся до этого служебного сообщения (исполнив все предыдущие, часть которых тоже может быть продолжением других async-await пар).
Все остальные, впрочем, тоже
Формально верно, но низкоуровневые детали лучше переформулировать. А то ведь и вправду поверят.
Re[2]: Почему не следует смешивать async-await и lock?
MD>Решение: чтобы UI не замерзал, надо дать ему возможность прокачивать очередь сообщений. На Delphi для этого был супер-метод Application.ProcessMessages, в визуал-бейсике тоже было что-то своё, и вот для C# спустя много лет был рождён async/await. Если почитать мануал про эту супер-фичу, то там вскользь говорится "на месте этих инструкций генерится служебная state-машина". Что это означает на самом деле? Банальный вызов старого-доброго PostMessage!
Ну вообще в C# супер-метод Application.ProcessMessages из Delphi называется Application.DoEvents еще с рождения. Async/await рожден позже является не совсем аналогом.
, всё зависит от конкретной реализации. Вариант по умолчанию использует текущий sync context, да.
Но это не значит, что по-другому оно работать не может.
Здравствуйте, Sinix, Вы писали:
S>Товарищи веб-девелоперы смотрят на этот коммент с некоторым недоумением
Создать очередь сообщений можно у любого потока, в том числе и не привязанного к HWND, так что IIS может спать спокойно Если склероз не изменяет, то в первых редакциях NET пул потоков был реализован именно на этом принципе, и только перед TPL его отрихтовали на awaitable-примитивы синхронизации.
S>Формально верно, но низкоуровневые детали лучше переформулировать. А то ведь и вправду поверят.
Понять саму ситуацию исходное описание вполне поможет. Кому надо — ощутит потребность "need to go deeper", тем более что в каждой версии Фреймворка оно может быть по-своему, включая всякие BCL-пакеты для исходно несовместимых осей (о, сколько даст открытий чудных async-await на WinXP...).
Здравствуйте, Mr.Delphist, Вы писали:
MD>Здравствуйте, Sinix, Вы писали:
S>>Товарищи веб-девелоперы смотрят на этот коммент с некоторым недоумением
MD>Создать очередь сообщений можно у любого потока, в том числе и не привязанного к HWND,
Абажите, а разве у каждого потока по умолчанию нету очереди сообщений? Хотя могу путать с очередями примитивов синхронизации, типа мониторов. Но, кмк, у потока по любому должна быть очередь, хотя бы для APC/DPC. Или нет?
Здравствуйте, Sharov, Вы писали:
MD>>Создать очередь сообщений можно у любого потока, в том числе и не привязанного к HWND,
S>Абажите, а разве у каждого потока по умолчанию нету очереди сообщений? Хотя могу путать с очередями примитивов синхронизации, типа мониторов. Но, кмк, у потока по любому должна быть очередь, хотя бы для APC/DPC. Или нет?
Исходно — очереди нет, ибо поток запросто может быть независимым воркером. Дали задачу, он и копает от забора до обеда. Закончил — терминируйся. И только уже в сложных сценариях типа всяких там ActiveObject, Actors и прочая и прочая возникает задача "держать руку на пульсе" и действовать в зависимости от свежих шифровок "Юстас — Алексу".
Re[4]: Почему не следует смешивать async-await и lock?
Здравствуйте, Sinix, Вы писали:
MD>>>Банальный вызов старого-доброго PostMessage! S>>Мож я чего не понимаю, но где тут PostMessage?
S>примерно там же, где и вот тут
, всё зависит от конкретной реализации. Вариант по умолчанию использует текущий sync context, да. S>Но это не значит, что по-другому оно работать не может.
S>Также вот тут
Здравствуйте, Sinix, Вы писали:
S>Правильное — не использовать блокировки, они в async-коде в 99% случаев нафиг не нужны. Вместо этого достаточно собрать все параллельно выполняющиеся ветки в Task.WhenAll(), дождаться завершения + продолжить выполнение в гарантированно чистом состоянии без гонок.
Можете показать пример — как из кода, где нужен асинхронный лок, сделать код, который соберет все ветки и дождется выполнения?