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...
Пока на собственное сообщение не было ответов, его можно удалить.