Почему не следует смешивать async-await и lock?
От: another_coder Россия  
Дата: 25.04.16 07:15
Оценка:
Я читал о том, что подобный код нормально работать не будет
object lockObj = new object()
lock(lockObj)
{
  await AsyncMethod();
}

и такой, наверно, тоже
public void Method()
{
  Task.Factory.Run(async () => {
    SyncMethod(); // with lock
    await AnotherAsynMethod();
  });
}

object lockObj = new object()
void SyncMethod(){
  lock(lockObj)
  {
    // .. do somthing
  }
}

Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её?
Re: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 25.04.16 07:30
Оценка: 16 (3)
Здравствуйте, another_coder, Вы писали:

_>Я читал о том, что подобный код нормально работать не будет


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

Проблема раз: продолжить работу код может в любом потоке (и для orleans — на любой машине), не обязательно в том, в котором захватил лок.

Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.


Формальное неправильное решение: SemaphoreSlim.WaitAsync

Правильное — не использовать блокировки, они в async-коде в 99% случаев нафиг не нужны. Вместо этого достаточно собрать все параллельно выполняющиеся ветки в Task.WhenAll(), дождаться завершения + продолжить выполнение в гарантированно чистом состоянии без гонок.
Не, в отдельных случаях может оказаться удобнее асинхронная очередь или любой другой producer-consumer, но это именно исключения.
Re: Почему не следует смешивать async-await и lock?
От: xy012111  
Дата: 25.04.16 07:33
Оценка:
Здравствуйте, another_coder, Вы писали:

_>Я читал о том, что подобный код нормально работать не будет

_>Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её?

А погуглить lock async c# пробовали?
Re[2]: Почему не следует смешивать async-await и lock?
От: another_coder Россия  
Дата: 25.04.16 08:12
Оценка:
Здравствуйте, xy012111, Вы писали:

X>Здравствуйте, another_coder, Вы писали:


_>>Я читал о том, что подобный код нормально работать не будет

_>>Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её?

X>А погуглить lock async c# пробовали?


сноска про удачную линку с понятным объяснением возникла после гугла
Отредактировано 25.04.2016 8:13 another_coder . Предыдущая версия .
Re[2]: Почему не следует смешивать async-await и lock?
От: another_coder Россия  
Дата: 25.04.16 08:23
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.


Я правильно понимаю, что в этом случае возникнет deadlock?
Re[3]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 25.04.16 08:44
Оценка:
Здравствуйте, another_coder, Вы писали:


S>>Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.


_>Я правильно понимаю, что в этом случае возникнет deadlock?

Неа, картина такая
// Задача1
lock(Smth)
{
  // освободили поток, лок не освобождён, другая задача выполняет
  lock(Smth)
  {
    // лок захвачен повторно из-за reentrancy.
  }
}
Re[3]: Почему не следует смешивать async-await и lock?
От: xy012111  
Дата: 25.04.16 09:11
Оценка: -2
Здравствуйте, another_coder, Вы писали:

_>>>Я читал о том, что подобный код нормально работать не будет

_>>>Только не понимаю почему. Объясните или если у кого есть удачная линка под рукой, рассказывающая про это, киньте её?
X>>А погуглить lock async c# пробовали?

_>сноска про удачную линку с понятным объяснением возникла после гугла


То есть, вы сами из довольно большого списка объяснений не смогли выбрать удовлетворившее вас и понадеялись, что кто-то "наугад" (не знаю, что для вас является "удачным") поможетт?

Может, удачнее будет выбрать какую-то более понятную статью и попросить здесь уточнить не ясные вам, спорные на ваш взгляд конкретные моменты в этой статье?
Re[4]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 25.04.16 09:24
Оценка:
Здравствуйте, xy012111, Вы писали:


X>Может, удачнее будет выбрать какую-то более понятную статью и попросить здесь уточнить не ясные вам, спорные на ваш взгляд конкретные моменты в этой статье?

А вот с удовольствием посмотрел бы на подробную статью по теме.

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

А то как всегда: пока, скажем, на вот эти грабли не наступишь — не узнаешь.
Re[4]: Почему не следует смешивать async-await и lock?
От: another_coder Россия  
Дата: 25.04.16 09:45
Оценка:
Здравствуйте, 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?
От: Mr.Delphist  
Дата: 25.04.16 10:06
Оценка: 4 (2) +1 -3
Здравствуйте, another_coder, Вы писали:

Эх, карате тут не поможет — надо было больше писать на 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?
От: Sharov Россия  
Дата: 25.04.16 10:20
Оценка: +2
Здравствуйте, another_coder, Вы писали:

_>Здравствуйте, Sinix, Вы писали:


S>>Проблема два: другая задача, запущенная в том же потоке, получает захваченный лок в наследство — повторный lock выполнится успешно, т.к. lock поддерживает reentrancy.


_>Я правильно понимаю, что в этом случае возникнет deadlock?


Нет, deadlock не возникнет, а вот race condtion и unexpexted behavior запросто.
Кодом людям нужно помогать!
Re[2]: Почему не следует смешивать async-await и lock?
От: Sharov Россия  
Дата: 25.04.16 10:34
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Банальный вызов старого-доброго PostMessage!


Мож я чего не понимаю, но где тут PostMessage?
Кодом людям нужно помогать!
Re[2]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 25.04.16 10:53
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Само собой, это сильно упрощённый псевдо-код, но суть явления не меняется: когда встречается await, это означает, что в очередь сообщений окна кладётся служебное сообщение.

Товарищи веб-девелоперы смотрят на этот коммент с некоторым недоумением

MD>И продолжение исполнения кода случится ровно тогда, когда оконная процедура доберётся до этого служебного сообщения (исполнив все предыдущие, часть которых тоже может быть продолжением других async-await пар).

Все остальные, впрочем, тоже

Формально верно, но низкоуровневые детали лучше переформулировать. А то ведь и вправду поверят.
Re[2]: Почему не следует смешивать async-await и lock?
От: Vasiliy2  
Дата: 25.04.16 10:56
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:


MD>Решение: чтобы UI не замерзал, надо дать ему возможность прокачивать очередь сообщений. На Delphi для этого был супер-метод Application.ProcessMessages, в визуал-бейсике тоже было что-то своё, и вот для C# спустя много лет был рождён async/await. Если почитать мануал про эту супер-фичу, то там вскользь говорится "на месте этих инструкций генерится служебная state-машина". Что это означает на самом деле? Банальный вызов старого-доброго PostMessage!



Ну вообще в C# супер-метод Application.ProcessMessages из Delphi называется Application.DoEvents еще с рождения. Async/await рожден позже является не совсем аналогом.

UPD. Да и в бейсике, кстати, он так же назывался
Отредактировано 25.04.2016 10:59 Vasiliy2 . Предыдущая версия .
Re[3]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 25.04.16 10:57
Оценка: 5 (1)
Здравствуйте, Sharov, Вы писали:


MD>>Банальный вызов старого-доброго PostMessage!

S>Мож я чего не понимаю, но где тут PostMessage?

примерно там же, где и вот тут
Автор: Sinix
Дата: 25.04.14
, всё зависит от конкретной реализации. Вариант по умолчанию использует текущий sync context, да.
Но это не значит, что по-другому оно работать не может.

Также вот тут
Автор: Sinix
Дата: 20.03.15
где-то обсуждалось.
Re[3]: Почему не следует смешивать async-await и lock?
От: Mr.Delphist  
Дата: 25.04.16 12:21
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Товарищи веб-девелоперы смотрят на этот коммент с некоторым недоумением


Создать очередь сообщений можно у любого потока, в том числе и не привязанного к HWND, так что IIS может спать спокойно Если склероз не изменяет, то в первых редакциях NET пул потоков был реализован именно на этом принципе, и только перед TPL его отрихтовали на awaitable-примитивы синхронизации.

S>Формально верно, но низкоуровневые детали лучше переформулировать. А то ведь и вправду поверят.


Понять саму ситуацию исходное описание вполне поможет. Кому надо — ощутит потребность "need to go deeper", тем более что в каждой версии Фреймворка оно может быть по-своему, включая всякие BCL-пакеты для исходно несовместимых осей (о, сколько даст открытий чудных async-await на WinXP...).
Re[4]: Вопрос, наверное, дурацкий.
От: Sharov Россия  
Дата: 25.04.16 12:54
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Здравствуйте, Sinix, Вы писали:


S>>Товарищи веб-девелоперы смотрят на этот коммент с некоторым недоумением


MD>Создать очередь сообщений можно у любого потока, в том числе и не привязанного к HWND,


Абажите, а разве у каждого потока по умолчанию нету очереди сообщений? Хотя могу путать с очередями примитивов синхронизации, типа мониторов. Но, кмк, у потока по любому должна быть очередь, хотя бы для APC/DPC. Или нет?
Кодом людям нужно помогать!
Re[5]: Вопрос, наверное, дурацкий.
От: Mr.Delphist  
Дата: 25.04.16 15:02
Оценка: 10 (1)
Здравствуйте, Sharov, Вы писали:

MD>>Создать очередь сообщений можно у любого потока, в том числе и не привязанного к HWND,


S>Абажите, а разве у каждого потока по умолчанию нету очереди сообщений? Хотя могу путать с очередями примитивов синхронизации, типа мониторов. Но, кмк, у потока по любому должна быть очередь, хотя бы для APC/DPC. Или нет?


Исходно — очереди нет, ибо поток запросто может быть независимым воркером. Дали задачу, он и копает от забора до обеда. Закончил — терминируйся. И только уже в сложных сценариях типа всяких там ActiveObject, Actors и прочая и прочая возникает задача "держать руку на пульсе" и действовать в зависимости от свежих шифровок "Юстас — Алексу".
Re[4]: Почему не следует смешивать async-await и lock?
От: Sharov Россия  
Дата: 25.04.16 15:58
Оценка: +1
Здравствуйте, Sinix, Вы писали:

MD>>>Банальный вызов старого-доброго PostMessage!

S>>Мож я чего не понимаю, но где тут PostMessage?

S>примерно там же, где и вот тут
Автор: Sinix
Дата: 25.04.14
, всё зависит от конкретной реализации. Вариант по умолчанию использует текущий sync context, да.

S>Но это не значит, что по-другому оно работать не может.

S>Также вот тут
Автор: Sinix
Дата: 20.03.15
где-то обсуждалось.


Да, тупанул. Забыл, что там пляски вокруг контекста синхронизаций, и все это обыгрывается через автомат.
Кодом людям нужно помогать!
Re[2]: Почему не следует смешивать async-await и lock?
От: IncremenTop  
Дата: 10.02.19 10:43
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Правильное — не использовать блокировки, они в async-коде в 99% случаев нафиг не нужны. Вместо этого достаточно собрать все параллельно выполняющиеся ветки в Task.WhenAll(), дождаться завершения + продолжить выполнение в гарантированно чистом состоянии без гонок.


Можете показать пример — как из кода, где нужен асинхронный лок, сделать код, который соберет все ветки и дождется выполнения?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.