Почему не следует смешивать 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(), дождаться завершения + продолжить выполнение в гарантированно чистом состоянии без гонок.


Можете показать пример — как из кода, где нужен асинхронный лок, сделать код, который соберет все ветки и дождется выполнения?
Re[3]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 10.02.19 11:13
Оценка:
Здравствуйте, IncremenTop, Вы писали:

S>>Правильное — не использовать блокировки, они в async-коде в 99% случаев нафиг не нужны.

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

Для сферического случая не могу, решения могут быть разные. Основная идея такая: отделить обработку от логики, которая требует синхронизации (например запись/ обновление данных). И вынести синхронизацию в часть, которая выполняется в одном потоке (например, в обработчике очереди или в коде после Task.WhenAll(). Как пример, чем оно полезно в нагруженных приложениях:
https://ayende.com/blog/164869/transaction-merging-locks-background-threads-and-performance
https://ayende.com/blog/179940/ravendb-4-0-unsung-heroes-the-indexing-threads
Re[2]: Почему не следует смешивать async-await и lock?
От: okon  
Дата: 10.02.19 12:58
Оценка:
S>Проблема раз: продолжить работу код может в любом потоке (и для orleans — на любой машине), не обязательно в том, в котором захватил лок.

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


Не совсем понятно где тут проблема именно для async/await, lock вначале захвачен и ради бога, если мы в другом потоке не натыкаемся на тот же lock то как это может вызвать проблему ?
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[3]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 10.02.19 13:03
Оценка: +2
Здравствуйте, okon, Вы писали:

O>Не совсем понятно где тут проблема именно для async/await, lock вначале захвачен и ради бога, если мы в другом потоке не натыкаемся на тот же lock то как это может вызвать проблему ?


Упрощённо и коротко, lock построен поверх Monitor.Enter/Exit() и привязан к потоку, async-и по определению не привязаны.
Re[4]: Почему не следует смешивать async-await и lock?
От: okon  
Дата: 10.02.19 13:16
Оценка:
Здравствуйте, Sinix, Вы писали:

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


O>>Не совсем понятно где тут проблема именно для async/await, lock вначале захвачен и ради бога, если мы в другом потоке не натыкаемся на тот же lock то как это может вызвать проблему ?


S>Упрощённо и коротко, lock построен поверх Monitor.Enter/Exit() и привязан к потоку, async-и по определению не привязаны.


Ну то есть мы получаем условно


Monitor.Enter();

Do_State0();


case state1 : 
       Do_State1()
           break;

case state2 :
       Do_State2()
           break;

...
case state2 :
       Do_StateN()
           break;

Monitor.Exit()



если в Do_StateX() не вызывается Monitor.Enter() то блокировок не должно быть
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Отредактировано 10.02.2019 13:16 okon . Предыдущая версия .
Re[4]: Почему не следует смешивать async-await и lock?
От: IncremenTop  
Дата: 10.02.19 16:36
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Для сферического случая не могу, решения могут быть разные. Основная идея такая: отделить обработку от логики, которая требует синхронизации (например запись/ обновление данных). И вынести синхронизацию в часть, которая выполняется в одном потоке (например, в обработчике очереди или в коде после Task.WhenAll().


Не понял честно говоря из тех постов.
Предположим, что есть такой условный псевдокод кэша:
    class Cache
    {
        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

        public async Task<List<T>> GetAll()
        {
            if (DateTime.Now - _cashedTime > _cachePeriod)
            {
                await _semaphore.WaitAsync();
                try
                {
                    if (DateTime.Now - _cashedTime > _cachePeriod)
                    {
                        await UpdateAsync();
                    }
                }
                finally
                {
                    _semaphore.Release();
                }
            }
            return _cached;
        }

        private async Task UpdateAsync()
        {
            _cached.Clear();
            _cached = await _service.GetAllAsync();
            _cashedTime = DateTime.Now;
        }
    }


Логику можно выделить, но не вижу чем это поможет в деле возможного разруливания доступа.
Re: Почему не следует смешивать async-await и lock?
От: MozgC США http://nightcoder.livejournal.com
Дата: 10.02.19 23:50
Оценка: 7 (3) +2
Здравствуйте, another_coder, Вы писали:

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

object lockObj = new object()
lock(lockObj)
{
  await AsyncMethod();
}


Такой код даже не скомпилируется. Комплилятор запрещает использование await внутри lock().

Почему?

Рассмотрим, что происходит в строчке "await AsyncMethod()".
Допустим, у нас такой код:

async void SomeMethod()
{
  doSomething1();
  await AsyncMethod();
  doSomething2();
}

Task AsyncMethod()
{
  return Task.Run(() => { do something on another thread })
}


На месте кода "await AsyncMethod()" компилятором будет сделано следующее:

Вначале компилятор создаст конечный автомат. Это будет просто объект класса, реализующий интерфейс типа IAsyncStateMachine. При создании этого конечного автомата будет произведена попытка захвата текущего контекста синхронизации. Этот контекст синхронизации будет использоваться при продолжении выполнения кода, следующего после await. Поэтому, если await выполняется например в UI потоке, то и продолжение выполнения будет произведено в этом же UI потоке. Если контекста синхронизации на момент создания конечного автомата нет, то выполнение продолжится в том же потоке, в котором выполнялся и асихронный метод (т.е. в нашем примере это то, что вызывается внутри Task.Run()). Поэтому, кстати, можно нечаянно нежелательно нагрузить ThreadPool тяжелыми операциями (что не советуется).

После того, как контекст синхронизации и локальные переменные захвачены (чтобы можно потом было восстановить исполнение кода со старыми значениями локальных переменных), компилятор выполнит первый шаг конечного автомата. На этом шаге в нашем примере будет запущен Task, который запустит выполнение кода в другом потоке (это может быть thread pool, по-умолчанию, или может быть создан отдельный поток, если Task указан как long-running).

После этого метод SomeMethod() как бы досрочно прерывает выполнение. Т.е. можно представить, что перед doSomething2() стоит return; На самом деле, компилятор перенесет код, следующий после await, в конечный автомат.
Куда происходит возврат исполнения? Ну, например, если метод SomeMethod() — это какой-то UI-обработчик, то мы вернемся в windows message loop, где продолжим обрабатывать следующие события из очереди.
Выполнение SomeMethod() будет возвращено, когда Task внутри AsyncMethod закончит работу и будет выполнен следующий шаг конечного автомата. Как я написал выше, поток, в котором будет возвращено исполнение будет зависеть от того, был ли контекст синхронизации на момент создания конечного автомата.

После того, как мы помедитировали над написанным выше, вернёмся к вопросу почему нельзя использовать await внутри lock();

Как мы тепрерь знаем, выполнение может продолжиться не в том же потоке, в котором мы захватили lock. Если обмануть компилятор, и вместо lock написать явно вызовы Monitor.Enter() и Monitor.Exit(), то если выполнение продолжится в другом потоке, то Monitor.Exit() выбросит исключение. Даже если бы Monitor.Exit() не выбрасывал исключение, то это уже неправильно, что в коде, который должен целиком выполняться только одним потоком (раз мы хотим использовать lock), вдруг оказался другой поток.

Поэтому использование await внутри lock запретили.
Отредактировано 10.02.2019 23:59 MozgC . Предыдущая версия .
Re[5]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 11.02.19 08:09
Оценка: +1
Здравствуйте, IncremenTop, Вы писали:

IT>Не понял честно говоря из тех постов.

IT>Предположим, что есть такой условный псевдокод кэша:

Конкретно тут совсем просто. Immutable state + compare-and-swap решают проблему для синхронного получения значений.

Immutable state делаем сами, CAS нам даёт ConcurrentDictionary (если значение одно, то Interlocked.CompareExchange()).
Для асинхронной логики нужен ещё AsyncLazy, он позволит нам использовать готовый код и не изобретать свою concurrent-коллекцию.

Что-то типа (набросал за 5 минут, могут быть ошибки)
        // THANKSTO: https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/
        public class AsyncLazy<T> : Lazy<Task<T>>
        {
            public AsyncLazy(Func<T> valueFactory) :
                base(() => Task.Factory.StartNew(valueFactory))
            { }

            public AsyncLazy(Func<Task<T>> taskFactory) :
                base(() => Task.Factory.StartNew(taskFactory).Unwrap())
            { }
        }

        public class UsesCache
        {
            private readonly ConcurrentDictionary<Guid, AsyncLazy<CacheData>> _cache = new ConcurrentDictionary<Guid, AsyncLazy<CacheData>>();

            private readonly TimeSpan _cacheTimeout = TimeSpan.FromMinutes(10);

            private async Task<CacheData> CreateState(Guid key)
            {
                await Task.Delay(123); // simulate async
                return new CacheData
                {
                    Key = key,
                    CreatedOnUtc = DateTime.UtcNow,
                    OtherProps = "OtherData"
                };
            }

            private AsyncLazy<CacheData> CreateStateWrapper(Guid key)
            {
                return new AsyncLazy<CacheData>(() => CreateState(key));
            }


            public async Task<CacheData> GetAsync(Guid key)
            {
                var temp = await _cache.GetOrAdd(key, k => CreateStateWrapper(k)).Value;
                var elapsed = DateTime.UtcNow - temp.CreatedOnUtc;
                if (elapsed > _cacheTimeout)
                {
                    _cache.TryRemove(key, out _);
                    temp = await _cache.GetOrAdd(key, k => CreateStateWrapper(k)).Value;
                }

                return temp;
            }
        }

        public class CacheData
        {
            public Guid Key { get; set; }
            public DateTime CreatedOnUtc { get; set; }

            public string OtherProps { get; set; }
        }

оверхеда практически нет: при гонке мы можем создать, но не использовать инстанс AsyncLazy (объяснение).
Re[5]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 11.02.19 08:15
Оценка:
Здравствуйте, okon, Вы писали:

O>Ну то есть мы получаем условно

O>если в Do_StateX() не вызывается Monitor.Enter() то блокировок не должно быть

Всё верно за исключением одного момента: Do_StateX() может быть вызван из потока, который не владеет блокировкой. Вот объяснение от классика
Re: Почему не следует смешивать async-await и lock?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 11.02.19 08:20
Оценка:
Здравствуйте, another_coder, Вы писали:

Там в общем то проблема в том, что монитор должен релизится в том потоке в котором вызван
await этого не гарантирует (ConfigwreAwait(true) при наличии контекста синхронизации.
Используй AutoResetEvent

AutoResetEvent lockObject = new AutoResetEvent(true);

………………

 try
            {
                lockObject.WaitOne();
                awaite что то там
            }
            finally
            {
                lockObject.Set();

            }
и солнце б утром не вставало, когда бы не было меня
Отредактировано 11.02.2019 8:25 Serginio1 . Предыдущая версия .
Re[6]: Почему не следует смешивать async-await и lock?
От: IncremenTop  
Дата: 11.02.19 11:56
Оценка:
Здравствуйте, Sinix, Вы писали:

S>оверхеда практически нет: при гонке мы можем создать, но не использовать инстанс AsyncLazy (объяснение).


Вопрос в том, что в изначально варианте подразумевалось обновление всего кэша. Но при обновлении всего кэша — придется и весь ConcurrentDictionary пересоздавать, что отрицательно скажется на производительности и получается тогда вариант с локом и обычной коллекцией не так плох?
Re[7]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 11.02.19 20:14
Оценка:
Здравствуйте, IncremenTop, Вы писали:

IT>Вопрос в том, что в изначально варианте подразумевалось обновление всего кэша. Но при обновлении всего кэша — придется и весь ConcurrentDictionary пересоздавать

Не, код со словарём — это уже продвинутый вариант. На случай, если надо несколько значений независимо друг от друга кэшировать.

Мне было влом писать решение для одного кэшируемого значения, т.к. там немного больше кода
Если коротко, то вместо _cache.GetOrAdd() надо использовать CAS loop, что-то типа вот этого
Re[8]: Почему не следует смешивать async-await и lock?
От: IncremenTop  
Дата: 12.02.19 12:57
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Если коротко, то вместо _cache.GetOrAdd() надо использовать CAS loop, что-то типа

вот этого

Придется применять Interlocked.CompareExchange по сути на каждый элемент коллекции при обновлении всей коллекции или же можно на всю коллекцию использовать? Не лучше ли в таком случае использовать семафор на всю коллекцию?
Отредактировано 12.02.2019 13:30 IncremenTop . Предыдущая версия .
Re[9]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 12.02.19 14:45
Оценка:
Здравствуйте, IncremenTop, Вы писали:

IT>Придется применять Interlocked.CompareExchange по сути на каждый элемент коллекции при обновлении всей коллекции или же можно на всю коллекцию использовать? Не лучше ли в таком случае использовать семафор на всю коллекцию?

Нет, коллекция хранится как свойство в CacheData. Зачем отдельно каждый элемент обновлять?
Re[2]: Почему не следует смешивать async-await и lock?
От: TK Лес кывт.рф
Дата: 13.02.19 21:53
Оценка:
Здравствуйте, Sinix, Вы писали:

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


с каких это пор?
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[3]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 14.02.19 10:42
Оценка:
Здравствуйте, TK, Вы писали:

TK>с каких это пор?

Емнип как минимум для stateless grains мб несколько activations => обработка после вызова grain to grain может продолжиться на другом silo. Это по памяти, давно шашки в руки не брал.
Re[4]: Почему не следует смешивать async-await и lock?
От: Danchik Украина  
Дата: 14.02.19 12:29
Оценка:
Здравствуйте, Sinix, Вы писали:

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


TK>>с каких это пор?

S>Емнип как минимум для stateless grains мб несколько activations => обработка после вызова grain to grain может продолжиться на другом silo. Это по памяти, давно шашки в руки не брал.

Sinix, как тебе впечатления от работы Orleans? Годная штука или лучше акторы самому городить?
Re[5]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 14.02.19 13:13
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Sinix, как тебе впечатления от работы Orleans? Годная штука или лучше акторы самому городить?


Я основательно копался с orleans года два-три назад, так что всё могло устареть. Лично я бы связывался только из интереса (под капотом там куча интересных решений).

Почему раз: довольно узкая ниша. Вот древняя презенташка, которая подсвечивает основные моменты.
Почему два: с вопросами документации, деплоймента и администрирования всё как всегда. В теории есть, на практике куча моментов требует своего велосипеда. Деплой, к примеру, не автоматизирвоан вообще.

Я бы начал с готовой системы (почти в любой компании уже есть свой велосипед) и или развивал бы её, или смотрел бы на Service Fabric.
Re[6]: Почему не следует смешивать async-await и lock?
От: Danchik Украина  
Дата: 14.02.19 13:45
Оценка:
Здравствуйте, Sinix, Вы писали:

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


D>>Sinix, как тебе впечатления от работы Orleans? Годная штука или лучше акторы самому городить?


S>Я основательно копался с orleans года два-три назад, так что всё могло устареть. Лично я бы связывался только из интереса (под капотом там куча интересных решений).


S>Почему раз: довольно узкая ниша. Вот древняя презенташка, которая подсвечивает основные моменты.


Вот почему узкая? Чего не хватает?

S>Почему два: с вопросами документации, деплоймента и администрирования всё как всегда. В теории есть, на практике куча моментов требует своего велосипеда. Деплой, к примеру, не автоматизирвоан вообще.


Да вроде в 2.0 как-то уже получше с документацией.

S>Я бы начал с готовой системы (почти в любой компании уже есть свой велосипед) и или развивал бы её, или смотрел бы на Service Fabric.


Service Fabric это в Azure, а тут надо кластер развернуть On-Promise.
Re[4]: Почему не следует смешивать async-await и lock?
От: TK Лес кывт.рф
Дата: 14.02.19 20:09
Оценка: :)
Здравствуйте, Sinix, Вы писали:

TK>>с каких это пор?

S>Емнип как минимум для stateless grains мб несколько activations => обработка после вызова grain to grain может продолжиться на другом silo. Это по памяти, давно шашки в руки не брал.

Эк вы себя запустили, батенька
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[5]: Почему не следует смешивать async-await и lock?
От: Sinix  
Дата: 14.02.19 21:18
Оценка:
Здравствуйте, TK, Вы писали:

TK>Эк вы себя запустили, батенька

Ага, освежил матчасть — фигню написал, миль пардон.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.