Как стать автором
Обновить

Комментарии 11

Интересная статья, спасибо.

Вопрос уточняющий - у вас Lazy и был и остался ExecutionAndPublication, а это насколько помню должно быть гарантией одного потока выполняющего сериализацию. Т.е. перевод с Lazy<tree> на Lazy<Task<tree>> исключительно чтобы вместо синхронного ожидания Lazy.Value было асинхронное, для разгрузки тредпула?

Короткий ответ - да.

Ответ посложнее - ожидание Lazy.Value до сих пор осталось синхронным. Вот только мы теперь синхронно ждём не всей длительной CPU-bound работы по сериализации дерева, а только создания Task (что почти мгновенно). И только затем все асинхронно ждут await task, да, не занимая потоки тредпула бессмысленными ждунами. (И ExecutionAndPublication здесь всё ещё нужен, чтобы случайно не создать два Task, оба из которых будут брать единицу параллелизма из семафора и сериализовывать одно и то же дерево.)

В стандартной библиотеке этого нет, но например в Nito.AsyncEx есть класс AsyncLazy специально для правильного комбинирования Task и Lazy.

И чтобы точно не блокировать потоки - здесь ещё неплохо бы сделать явный Task.Run() или Task.Yield() в начале лямбды. Иначе возможен вариант, когда SemaphoreSlim возьмёт лок без переключения потоков, мы в том же потоке провалимся в синхронную сериализацию, и все пришедшие за таской другие потоки будут висеть на локе в Lazy

Замечание про Task.Run (или Task.Yield(), что я менее предпочитаю) валидное. Спасибо за дополнение.

И чтобы точно не блокировать потоки - здесь ещё неплохо бы сделать явный Task.Run() или Task.Yield() в начале лямбды. Иначе возможен вариант, когда SemaphoreSlim возьмёт лок без переключения потоков, мы в том же потоке провалимся в синхронную сериализацию, и все пришедшие за таской другие потоки будут висеть на локе в Lazy

Это почти всегда некритично: значение Lazy(Task<SerializedTree>) создается очень быстро, после чего создавший его поток может смело уходить в длительную синхронную операцию сериализации (или даже блокироваться) - пострадает только он. Другие потоки, пришедшие за самим сериализованным значением - то есть, за результатом задачи - получат созданную задачу (возможно, после короткого ожидания) в незавершенном состоянии, т.е. при попытке сделать await на ней они будут ждать асинхронно - т.е. вернут свой поток, в котором они выполняются, в пул, а потом их продолжение перезапустится после завершения задачи в новом потоке из пула.

Я тут поэксперементировал немного (если интересно, могу код куда-нибудь типа на github закинуть) - все так оно и есть. Единственная возможная проблема (и я на нее наткнулся, потому что изначальнно сделал так) - если все задачи, которым нужно сериализованное значение, изначально запускаются как асинхронные методы из одного и того же потока: тогда запускающий поток, занятый сериализацией, не сможет их запустить. Но, к примеру, при обработки запроса к серверу на ASP.NET Core такая ситуация маловероятна: там обработчики разных запросов запускаются в разных потоках из пула самим фреймворком.

Всё так. В реальном мире это почти всегда не существенно. Task.Run - это так, для "формальной корректности и чистоты".

Статья о C# и .Net. Она о примитивах дотнета и их особенностях. Она показывает, как те или иные синтаксические конструкции сказываются на работе приложения.

И да, она показывает это на примере какого-то настоящего сервиса. И совершенно не важно какого, и что он делает. Если бы статья была об архитектуре приложений, а не о языке и фреймворке, тут были бы другие акценты. И совершенно верно, тут было бы интересно говорить про диск.

Вам только кажется :)

Мы всего лишь воспользовались не самыми глубокими знаниями о работе тредпула и применили несколько простых инструментов из фреймворка, чтобы эффективно решить очень конкретную задачу.

Несомненно, написать на C++ код, который выполняет длительную CPU-bound работу с ограниченным уровнем параллелизма, можно очень легко и просто. Я даже соглашусь, что это может быть проще, чем на C#.

Но помещая эту подзадачу внутрь лапши http-сервера, с возможностью кучи асинхронных вызовов по пути исполнения запроса, с обилием бизнес-логики, сравнить суммарные трудозатраты на разработку и длительную поддержку всего этого в совокупности на этих двух языках уже не так просто.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий