Pull to refresh

Comments 646

Понимаю, что автор статьи подошел с юмором :)

А теперь по сути, из личного опыта так сказать. Был свидетелем, когда в JS приходили люди из мира Java. Первое, что я слышал — «да ваш JS га… о. Нет типов? Да это убожество! А это еще и работает?». Первое что приходило в голову тогда — «дааа… походу люди какие-то фанатики Java и ничего другого не видят и видеть не хотят».

Т.е люди приходят в JS, считают, что JS это что-то типа для формочек в браузере. Не разбираются, как что работает, не вникают в тонкости и бегут на хабр/стековерфлоу/другой блог и давай писать — JS га… о! Да как на этом можно писать!?! JS девелоперы слепые фанатики!

Читал первую статью, потом вторую и вот третья появилась. Ребят, да оставьте кесарю кесарево. Вам удобно на C#/Java писать? Пишите, вас же никто не заставляет принудительно сменить стек технологий, потому что стильно/модно/молодежно.

PS Пишу на JS уже лет 6-7 и прошел путь от «что-то подкрутить в jQuery» до сложных SPA для ERP/CRM систем, а так же архитектурных решений на NodeJS на серверной части.

PSS пожалуйста, давайте без холивара.

Я просто пытаюсь намекнуть, что фанатизм не выражается в том, что кто-то с упоением пишет на JS. Большая часть статьи пестрит выражениями "ты думаешь" и "ты считаешь". Фанатизм — это больше психологическое. Он проявляется, когда человек начинает на каждом шагу проталкивать идею о том, что "мой язык — хороший, а все кто его не понимает — идиоты". Вот этот самый постфикс про идиотов и отличает фанатика от обычного разработчика. Я пытался вложить посыл "фуфуфу таким быть". :)

Так дело в том, что я наблюдаю фанатиков с обоих сторон. Кто-то пишет на Java и пытается доказать, что все остальное полная хрень и недостойно внимания и времени. Те, кто пишет на С# тоже пытаются доказать, что лучшего нет. И так далее, и так далее. И каждый с пеной у рта доказывает свою правоту, при этом пытается втоптать в дерьмо опоннента.

И ломаются копья, и пишутся посты гнева. Зачем? Что кто кому пытается доказать?

Честно вам скажу, я думал "хм, а ведь в статье JavaScript можно заменить на любой другой язык". Но вот сугубо ИМХО, сугубо из моего опыта — разработчики на JS ведут себя подобным образом чаще. Я знаю Java-разработчиков, я знаю C++-разработчиков. Я даже знаю Haskell-разработчиков. Но самые громкие из них — JS-разработчики. Вот просто так получается по моей, вероятно нерепрезентативной выборке.

Мне кажется, что это из-за того, что JS язык молодой во второй реинкарнации. Я имею ввиду, то, что как нормальный язык он стал только последние 5-6 лет с приходом ES6. И в него влились молодые люди, котороым стоит поумерить самомнение и преобрести терпимости. И я таким был, но ничего, мы взрослеем, набираемся опыта.

Думаю, надо научится уважать все стороны. И если кто-то кичится, то стоит человека на место ставить. Язык программирования тут не причем.

Тем не менее я убежден, что если человеку мягко не намекнуть что он ведет себя некрасиво — он сам не поймет.

Абсолютно с вами согласен. Но еще раз уточню — язык программирования тут не причем.

… о чем и написано в шапке статьи.

UFO just landed and posted this here

Да там ничего интересного. Просто атрофируется возможность писать на языках без монад и к 8 уровню хочется строжайшей типизации для всего подряд, e.g. хочется иметь тип "циферки от 5 до 10.5"

UFO just landed and posted this here

Ну включите саморефлексию и напишите :) Может мне еще и яичницу за вас приготовить? :)

UFO just landed and posted this here

Рефлексия упрощенно — саомкопание. "Саморефлексия" это уже что-то принципильно новое ;D дедлок какой-то. [/зануда]
Простите не удержался.

UFO just landed and posted this here

Без вики Вас не понял)) Да все верно, мое занудство ущемлено =)

Пишу (или когда-то писал) на Perl, PHP, JS, Java, C#, C++, Bash, XSLT, Python, ActionScript, Groovy, Lua. Наверняка что-то забыл упомянуть.


Во-первых, все вышеперечисленное дрянь, по тем или иным причинам :) Во-вторых, неперечисленное тоже дрянь.

От каждого по его способностям, каждому — по его труду ©

Пишу на 1С и не парюсь. Зачем все остальные языки?!
В случае 1С — это уже 8 уровень )))

А еще есть те, кто говорит «язык X — говно, но я на нём зарабатываю больше, чем на Y, поэтому на нём и пишу»...

Согласен с Вами, коллега, от холиваров фанатиков нет никакой пользы окружающим…
P.S. Сам я веб разработчик asp .net (C#), недавно дома начал пробовать писать игры на JS, сначала использовал jQuery, потом отказался от него, чтобы лучше изучить JS. Но ни востограюсь никакими языками и принципами, самое главное в нашем деле — простота и изящество решений, имхо…
Вот этот самый постфикс про идиотов и отличает фанатика от обычного разработчика

Ваша агрессивность фанатична

Заметьте, вовсе не js-разработчики всё это на Хабре начали.

Тоесть это адаптация этой статьи на тему JS?)
Пишу на JS уже лет 6-7

Бедненький, такое не лечится.
jQuery — удобный инструмент и искренне тебе помогает в делах

jQuery это устаревший инструмент, имеющий ряд недостатков. Для своего времени это был прорыв, позволяющий писать кроссбраузерный код без уродливых конструкций типа
 var ua = window.navigator.userAgent;
  var msie = ua.indexOf ( "MSIE " );

  if ( msie > 0 )      // If Internet Explorer, return version number
     return parseInt (ua.substring (msie+5, ua.indexOf (".", msie )));
  else                 // If another browser, return 0
     return 0;

да, так писали в 2010 году
Это был один из его плюсов тогда, но сегодня этого всего уже не нужно. Теперь о минусах, с jQuery очень легко писать с нуля, но очень тяжело поддерживать, потому что очень тяжело выявлять связи между кодом js и html разметкой. Как только проект становится больше hello world, очень легко, что то сломать и даже об этом не знать, потому что ошибок в консоли скорей всего не будет.

Слушайте, ей-богу, ваше мнение о jQuery тут никого не интересует. В контексте статьи фраза про jQuery означала "тебе комфортно в jQuery и ты не чувствуешь потребности в чем-то большем". Пожалуйста, смогите в переносный смысл. Я в вас верю.

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

так что пускай люди пишут свои мнение
и ради бога, не думайте за всех
не пишите «никого не интересует»
Людей который слишком много кричат про «jQuery не нужен» можно довольно легко ловить на простую двуходовочку:
1. Просим их показать аналог jQuery.position()
Они говорят да вот же!!1, смотрите как просто!
2. А теперь нас интересуют координаты элемента, который обернут в элемент с position: absolute, а один из его родителей имеет position: fixed, и еще какой-то из родителей relative и там еще где-то margin'ов понапихано.

Это в общем-то можно всё разрулить, но будет не она строчка, а десятка полтора. И по смыслу не будет отличаться от того, что внутри у jQuery.
А мы вот возьмем и вынесем эту функцию в новую библиотеку — шах и мат.
А потом вторую, третью, пятую, десятую…
Ну а там и до пакетного менеджера недалеко, чтобы управлять ими.

То есть, вы предлагаете, если нужно использовать одну функцию из jQuery, тянуть всю jQuery в проект, если нужна одна функция из lodash, тянуть весь lodash и т.д.?

Ну вот смотрите: C/C++ компиляторы выбрасывают неиспользуемый код при компиляции, и для этого не нужно дробить библиотеки на элементарные. А вот в JS из-за особенностей языка так сделать нельзя, вот и приходится делать надстройки в виде пакетных менеджеров на каждый чих.

UFO just landed and posted this here
Если реально одну — нет, но тогда и новая библиотека не нужна.
Однако практика говорит, что одной дело ограничивается очень редко.

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

А еще «use jQuery» это известный мем на stackoverflow. По-моему в контексте статьи смотрится весьма органично. :)
нет никаких трудностей, если для обращений к элементам вы используете только id вместо html разметки и css классов

как только вы фокусируетесь на id элементов, то ваша работа сводится к работе с моделью приложения и javascript + jquery, консоль в таком варианте незаменима

проблемы большого проекта решаются разработкой грамотной модели приложения, а не отказом от jquery
Асинхронные операции в Node.JS для тебя — cutting edge. Ты полностью уверен, что ни в одном другом языке кроме как JavaScript это невозможно и никаким другим фреймворком кроме Node.JS это не поддерживается

Хотя бы в ноде он (async/await) работает так, как ты этого ожидаешь, в отличие например от .net

… и вот мы определили ваш уровень поражения вирусом. :)

Не ставьте диагнозы ;) Пишите больше на js или лучше на ts, который на мой взгляд взял лучшее от js и c#. И тогда вы поймете, что то, что вам казалось разжижением мозга, на самом деле просветление.

Вы видимо шапку статьи не прочитали.

ts, который на мой взгляд взял лучшее от js и c#

Чем ts лучше c#? =) Как по мне, ts хоть и улучшает js значительно, но до c# очень не дотягивает

Э… и в чем же отличие того как работает async/await в js и в .net? :-)

Тот же вопрос: в чём разница? Когда запустил NodeJS — понравилось, что async/await работает также как в C# (даже подумал: близнецы-братья), разве что дедлоков нет по Wait() или Result() — если синхронные операции вызываешь в асинхронном методе.

Ну как же ты не видишь, слепец! В js это работает с магией, а в этом твоём c# работает с энтерпрайзным унынием! Как вообще это можно спутать?!

Смех смехом, а существенная разница всё же есть: в C# для async операции создаётся отдельный поток, а в JS остаётся один поток. Если в JS async функцию поместить какую-либо долгую (не IO) операцию, например вычисления, не заблокирует ли она главный поток: habrahabr.ru/company/jugru/blog/341070/#comment_10499826? Нужно ли её как-то по особенному писать?

В C# асинхронные функции никто в отдельный поток автоматически не выпинывает.

В прямом. Вызов функции, объявленной как async, выполняется без создания дополнительного потока.

Но прятать такие штуки внутри асинхронных функций — дурной тон.
Это понятно, в основном конечно используется для ввода-вывода. Но C# потоки всё же использует для этого.
Создание потоков — это особенность не C#, а стандартной библиотеки. Потоки создаются явным образом внутри самих асинхронных функций и/или в планировщике.

Например, мой собственный планировщик асинхронных операций, написанный на C#, является полностью однопоточным.

Ну да, потому что ни Wait, ни Result в js у обещаний нет :-)

Тот же вопрос: в чём разница?

разве что дедлоков нет

Хммм… действительно… в чём же разница… ;-)
разве что дедлоков нет по Wait() или Result() — если синхронные операции вызываешь в асинхронном методе.

Эта проблема решается просто: не используем синхронные операции вообще и используем свой планировщик. Да-да, механизм async-await — это просто синтаксический сахар, что в C# (обёртка над Task<>), что в JS (обёртка над Promise). Вам ничто не мешает в C# здесь так, чтобы абсолютно всё выполнялось в одном потоке, и никакие Wait() и Result() не будут нужны.

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

Но это же не повод делать дедлок...

Всё таки существенная разница есть: в C# для async операции создаётся отдельный поток, а в JS остаётся один поток. Если в JS async функцию поместить какую-либо долгую (не IO) операцию, например вычисления, не заблокирует ли она главный поток: habrahabr.ru/company/jugru/blog/341070/#comment_10499826? Нужно ли её как-то по особенному писать?
Я по причине обучения в ВУЗе столнулся с JS, до этого долго и упорно сидя на Lua. Ну первое время у меня матов не хватало, что бы описать JS, и если бы не jQuery, то возможно и сейчас бы не хватало.

У меня была та же реакция, когда я после JS врубался в Lua.

Слова вместо фигурных скобок не красиво соглашусь. То что функция может вернуть несколько результатов не привычно. Условия циклов и ветвлений можно обернуть в скобки чтобы выглядело привычно. То что всё можно использовать как ключь это круто. Правда пользовался этим только при сериалиации. То что конкатенация это отдельный оператор это плюс. С булевыми операциями всё просто. Всё true кроме false и nil.

«Всё как ключ» в общем удобно при работе с рекурсивными структурами. Не только сериализация, мне для конвертации в другие рекурсивные типы данных пригождалось (вроде из типов lua в C’шные структуры, используемые в другом языке программирования). Булевые операции в таком виде удобны, т.к. иногда позволяют обойтись без тернарного оператора. Но оператор был бы лучше.


Что мне категорически не нравится — это стандартная библиотека. io.popen не умеет даже такое малой части subprocess.Popen из Python как «не использовать shell» и «открыть одновременно на запись и чтение». setenv нет, хотя getenv есть. Конечно, в lua намеренно минимальная стандартная библиотека. Но я специально привёл примеры того, что я считаю присутствующим, но не полностью реализованным функционалом, а не тем, что вообще хотелось бы видеть в стандартной библиотеке.


Второе, что мне не нравится — совместимость. В lua её ломают, поэтому многие просто сидят на 5.1. Luajit до сих пор полностью совместим с 5.1, от 5.2 взято только то, что не нарушает совместимость, 5.3 в changelog вообще не упоминается. В Gentoo всё, кроме 5.1 замаскировано, maintainer’ы потихоньку монстрячат слотовый lua, чтобы ставить несколько версий lua одновременно — как я понял, множество пакетов в новой версии не работает и никто не хочет их чинить, поэтому выбирать не‐5.1 в качестве основной версии просто нельзя.

Нет, больше вымораживало отсутствие массивов.

Таблицы в Lua выполняют так-же роль массивов. В отличии от JS манипуляции с "массивами" выполняются внешними функциями но если очень нужно их можно и прицепить к таблице.


t = {"a", "b", "c"}
t.concat = table.concat
print (t:concat("*")) --> a*b*c

В JS то тоже чистых массивов нет. Это просто тот-же объект с дополнительными фунциями работы с массивом.


Таблица в Lua аналог обьекта в JS.

В PHP вроде тоже. Но это как‐то непривычно — я с Python и C на lua переходил. До 5.0, кстати, никаких оптимизаций для таких таблиц не было, всегда честный хэш (с соответствующей константой для вещей вроде table.insert), с 5.0 там гибрид массива и таблицы. table.insert всё равно, правда, не содержит оптимизаций вроде “если можно, просто запусти memmove()”, но гибридность снижает затраты на lua_rawget()+lua_rawset(). Тем не менее, судя по коду в 5.1 оператор # (длина) всё ещё О(log(N)). В 5.2—5.3 и luajit — не знаю.

Как‐то пытались использовать в Neovim, но отказались. Главная проблема: тесты на moonscript проваливались, никто не знал, почему. Другие проблемы:


  1. Если вы сделали ошибку в синтаксисе lua напишет, какая и где была ошибка. Если вы сделали ошибку в синтаксисе moonscript, вы получите «Failed to parse», номер строки и всё.
  2. Если вы сделали ошибку не в синтаксисе, то вы получите ошибку. А номер строки — нет, только номер строки в результирующем lua коде который в общем случае недоступен (вы можете сделать конвертацию локально, но вам нужна та же версия moonscript и, возможно, что‐то из окружения).
  3. Moonscript отсутствует в репозиториях различных дистрибутивов. А у нас не lua пакет, чтобы ставиться из luarocks, где moonscript есть.
  4. Линтеров практически нет. Я знаю только встроенный и moonpick, при чём второй не существовал во время принятия решения, а первый весьма ограничен.
  5. Мажорная версия языка до сих пор 0, что означает, что язык будет считаться нестабильным. (Сам автор сказал, что он не использует semantic versioning и не считает язык нестабильным сам, но это скорее ещё один минус.)
  6. Moonscript добавляет ещё один протекающий слой абстракции.
  7. Людей, которые могут писать на moonscript неизбежно меньше тех, что могут писать на lua, потому что первые — подмножество вторых, потому что без знания lua отлаживать moonscript код нельзя.

Соответствующая дискуссия, окончившаяся тем, что текущий (@tarruda сейчас фактически не участвует в разработке) лидер проекта сказал, что будет использоваться moonscript, хотя больше людей было за lua. Пример неизвестных проблем с moonscript. Другой пример.

У вас ссылка на «JavaScript как праздник» введет туда же, куда и «JavaScript как явление».
однако мой основной профиль — C# и делать всё, чтобы защитить бедных C# разработчиков от излишнего ныряния в JavaScript.
«Бедные» C# разработчики не обязаны нырять с головой в JavaScript, если компания позволяет себе нанять отдельно разработчиков C# и JavaScript. Статья написана конечно же не для этого (по моему впечатлению). По вашей статье складывается впечатление, что все, кто пишет на более новых технологиях, чем JQuery — «больны».
Есть в уровнях (особенно последних двух) некоторые описанные симптомы, которые действительно кажутся маразмом, но не все. Например:
Едешь на конференцию по JavaScript. Там тебе впервые показывают как за 20 секунд сделать сервер на Node.JS. Восторгаешься. Вирус, подпитавшись материалами с конференции откусывает ту часть мозга, которая еще помнила про многопоточность. Она помирает и не успевает задать свои вопросы
Объясните, разве это плохо — ездить на конференции по своему языку программирования? Плохо, что в какой то технологии можно за 20 секунд сделать что-то?
Хочу обратить внимание, что первым появился пост, который обсирает JavaScript, и негодует его резкой популярности, второй же пост просто защищает JavaScript.
«Здоровый» человек не будет обсирать другую технологию, которую он не понимает, которая стала вдруг популярной. «Здоровый» человек напишет про свою любимую технологию. Не хочешь JavaScript на backend, не применяй (если ты решаешь чему быть), убеди тех. директора чем C# лучше JavaScript, не работай в компании с «больными» людьми, и все остальное в этом духе.
Мне кажется больше разводят холивар не JavaScript разработчики, а те, кто не являются JavaScript разработчиками, но кому приходится им пользоваться, ибо он сильно отличается от других.
У вас ссылка на «JavaScript как праздник» введет туда же, куда и «JavaScript как явление».

Fixed


По вашей статье складывается впечатление, что все, кто пишет на более новых технологиях

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


Плохо, что в какой то технологии можно за 20 секунд сделать что-то?

Плохо, если технология скрывает от тебя очень важный аспект реализации чего-либо, а ты сам не желаешь разобраться как оно все-таки работает.


«Здоровый» человек не будет обсирать другую технологию

Пост как раз об этом. В шапке же написано, что я критикую фанатиков, а не технологию

Плохо, если технология скрывает от тебя очень важный аспект реализации чего-либо, а ты сам не желаешь разобраться как оно все-таки работает.
Простите, но разве не этим же самым заняты 99,99 любых библиотек на любых языках, да собственно и самих языков высокого уровня? Это же всё направлено на то, чтобы ты не воевал с инструментом за каждый шажок, а занимался решением своей прямой задачи.

Все верно. Однако — опять же — в хороших ВУЗах зачем-то изучают что такое стэк, как передаются параметры в функцию и вообще учат C. Если ты хотя бы ориентировочно понимаешь как и что работает — это помогает идентифицировать и обезвредить любой потенциальный факап, который может возникнуть с технологией. В случае многопоточного программирования — даже в node.js можно поймать race condition. И вот представьте что вы наткнулись на race condition, а вы не знаете что это :)

А что, в C нельзя устроить race condition, не понимая что это такое? Он настолько сложен, что без 5 лет учёбы в ВУЗе на нём нельзя сделать такую тривиальную вещь, как параллельный/асинхронный блок кода?
Да и тем более, race condition он что, спалит вам компьютер и аннигилирует вселенную, если вы за 5 секунд его не нейтрализуете? Для его устранения нужны какие-то сакральные знания, которые нельзя восполнить в пределах 15 минут?

Эаам…
Наоборот — на C очень легко сделать race condition с posix-тредами и натренироваться такие ситуации подмечать и бороться с ними.

Тогда я вообще не понимаю. Вы ругаете язык за то, что в нём сложнее отстрелить себе ногу, потому что отстреливая себе ногу гораздо легче научиться не отстреливать себе ногу?

Послушайте, я не ругаю языки. Я ругаю людей, которые не учатся основам перед тем, как начать делать что-то серьезное.

Да, для устранения race condition нужны знания, которые нельзя восполнить за 15 минут. Очень интересно наблюдать, как такие проблемы решают разработчики с завышенным самомнением — обвешивают костылями, а потом отмазываются — php наверное сбоит. Любые абстракции текут, поэтому фундамент нужно знать.

Я не очень понимаю.
C# / C++ / Haskell / Java / etc -разработчики активно нападают на JS.
Js-разработчики защищают свой язык.


Фанатики как раз первые, а вовсе не вторые. Почему вы написали пост про js-фанатиков, а не haskell-фанатиков или c#-фанатиков?

Я вот фанатиков от этих языков особо как-то не встречал. Сообщества ведут себя абсолютно адекватно, не строят из своих технологий серебрянную пулю и не считают быдлом всех, кто не пишет на %язык_нейм%, не общаются рекламными штампами. Кроме того, относительно адекватно реагируют на вопросы. E.g. каждый раз, когда я задавал вопрос по C++/Java любому из опытных разработчиков на оных — я спокойно получал ответ и разъяснение как и что работает. Каждый раз, когда я задавал вопрос по JS-у, я получал в ответ упреки, что я ничего не понимаю, мне надо учить самые основы и вообще "не умеешь — не лезь". Так один из адептов рассказывал мне снисходительным тоном про прототипное наследование — будто Америку открывает. Делал он это в ответ на вопрос о виртуализации прокрутки. И такие ситуации возникли несколько раз подряд — вот я и подумал что что-то с разработчиками на JS не так. Когда они начали отрицать индустриально-базовые вещи, такие как строгая типизация и многопоточность — я понял что под видом сакрального знания чаще всего подают вопиющую неквалифицированность. Ну я не выдержал и все заверте… Я просто скомпилировал все штампы, которые слышал в одну отдельную статью

UFO just landed and posted this here

Понимаете в чем дело, ни фанаты haskell, ни фанаты C++ не станут подвергать сомнению такие базовые дисциплины как операционные системы, сетевые технологии, базы данных или теория типов. В отличие от.


Они могут сказать что я пишу на не труъ-языке, но они никогда не скажут что многопоточность — это плохо.

Многопоточность это сложно, а сложно это плохо. Согласитесь, без неё мир был бы проще. Просто есть задачи, которые без многопоточности решаются еще сложнее. И кстати многие среды выполнения JS внутри используют потоки. Взять тот же node.js, не говоря уже о браузерах.

Промышленная химия — это сложно. А сложно это плохо. Согласитесь, без лекарств мир был бы проще.


Просто, как вы заметили, без лекарств людям жить еще хуже :)

Просто, как вы заметили,

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

Вы не поверите, но это реально круто, когда код можно тупо дебажить, покрывать юнит-тестами, а потом он реально так и работает.

Как вы покроете юнит-тестами конструкцию "показывать пользователю UI, давать работать с локальной копией БД, в то же время пытаться установить подключение к серверу с увеличивающимися интервалами в 10, 20, 30 сек и далее минута, но отвалиться после 5 попыток"?

Мы сейчас точно говорим про юнит-тест (это которые должны тестировать SUT, абстрагировав от всех внешних зависимостей?) Мне почему-то кажется, что функцию которую вы описали, надо покрывать матом — слишком много на себя берет. :)

Во всех реализациях JS таймер является внешней штуковиной, которая благодаря этому тривиально заменяется на фейк. Например такой http://legacy.sinonjs.org/docs/#clock. Все предельно просто, как в детском конструкторе, и это правда работает.

При чем здесь таймер? Я вам не про функцию говорю, а про целую систему. Вы можете покрыть 2 потока отдельными тестами, но интеграционно вам придется тестировать это вручную.

UFO just landed and posted this here

Извините, я на вашем клингонском не говорю :)

Ну для этого как минимум операции должны быть атомарными, а скорость роста всех возможных перестановок явно отобьет охоту проверять все возможные ситуации :)
Я не говорю еще про инвалидацию кэша процессоров, оптимизации компиляторов и пр.низкоуровневые вещи, меняющие выполнение операций.

Да, жизнь боль :(
UFO just landed and posted this here
Прикол в том, что 2 потока отдельными тестами это имитация бурной деятельности. 2 потока в тесте и 20К потоков при эксплуатации могут давать совершенно разную картину для одной и той же сборки. А это, в свою очередь, резонно порождает отдельную пачку вопросов относительно практической полезности и экономической оправданности таких тестов в контексте раннего обнаружения класса багов, специфичного лишь для многопточного кода.

Выпад про интеграционные тесты, если честно, не понял. Мой тезис был конкретно про юнит, ибо это база. Для end-to-end тестов в условиях приближенным к боевым, существуют свои решения как для ручного так и для автоматического тестирования.

Вот вы и сами видите. Фанат haskell не скажет, что многопоточность, это плохо. Он хотя бы про видеокарты вспомнит, там многопоточность — это хорошо. А вы сказали, даже объяснение придумали. В этом разница.

Фанат хаскель, если он конечно не разработчик ghc, скажет, что тождественный функтор для единичного морфизма это хорошо.) А многопоточность, ей богу — пофик — компилятор умный, вот пусть и думает как параллелить.
UFO just landed and posted this here
Тогда можно еще задачу комивояжора припомнить (и вообще там целый класс задач из мира «очевидное-невероятное программирование», ни чем не хуже, чем «1 + '2' == '2' + 1» в JS). А про фанатов это конечно под впечатлением шутливого тона статьи. Правда, если воспринимать буквы совсем буквально, то меня можно и канделябром (запрещено законом РФ). Только вместе с автором, ведь не я первый здесь это начал.
UFO just landed and posted this here
Сожалею, что пример оказался путанным. Я имел ввиду задачи, где глядя на код, результат не совсем всегда очевиден. В случае map fibonacci и задачи нахождения оптимального пути в графе в хаскеле получается неожиданно плохая асимптотика. По-моему это ни чем не лучше, чем приколы с приведением типов в JS.
UFO just landed and posted this here

… и эта самая мемоизирующая реализация все еще в связке с map будет работать медленнее чем хотелось бы, хоть и не будет уже так тормозить.

UFO just landed and posted this here

Если бы не было многопоточности, мы бы до сих пор сидели бы на древних компутерах.

Многопоточность — сложно по сравнению с использованием callback?
Многопоточность это сложно, а сложно это плохо

В программировании обычно наоборот. Просто это плохо, потому что на самом деле все сложно, но это просто скрывает от вас детали реализации и костыли, которые лежат внутри, а потом нужно это как-то решать, как только приходится делать что-то, о чем создатели этого просто не подумали. А так происходит почти всегда.


Вот и запускаем кучу процессов вместо одного и менеджим их вручную, вместо того, что бы нормально создавать пулы потоков.

Многопоточность — это не плохо. И не хорошо. Просто в рассуждениях о node.js, противопоставляя его многопроцессному подходу, смешивают в кучу «людей и коней».

Ведь все пользуются однопоточным nginx для статики, не так ли? Так вот. Всем же очевидны его преимущества? Так вот. Типовая модель использования node.js — та же: «принеси-подай» из базы. Накладных на преобразование http-запроса в sql/mongodb-запрос практически нет (ну не более чем nginx резолвит filename:)). Забрать из базы данные — почти та же статика, которая не содержит логики обработки по запросу (в отличие от типичного рендеринга в PHP). Чтение файла — такая же асинхронная операция, как и выборка.

Стандартный способ деплоя node.js — это pm2, который кластеризует процессы node по ядрам. По сути, мы перещаем бутылочное горлышко рендеринга из серверных процессов на клиента (в современных virtual dom-технологиях и серверах api на node.js по типу loopback). То есть нагрузку на сервер облегчаем, и это честно так.

И кстати, многопоточность — это плохо, когда накладные на переключение начинают влиять. Разница между процессами и потоками с точки зрения ОС небольшая (общая память => общий краш для потоков, для процессов нет), и вряд ли кто поспорит, что 4 процесса по 1000 коннектов лучше 4000 потоков (пусть даже не процессов) в системе. Банальный fork в линухах должен будет скопировать окружение процесса, проинициализировать процесс и прочие приседания на синхронизацию (где-то родительский поток ждет или поллит семафор заверешения чайлда).

Я вот не говорю, еще раз, что многопоточность плохо (или хорошо). Я говорю о том, что рассуждать о ней надо в контексте конкретной системы.
Ведь все пользуются однопоточным nginx для статики, не так ли?
Мир клином не сошелся на IO-bound задачах. Более того, IO-bound задач настолько мало, что их обычно изолируют и забывают (тот же nginx например).

Типовая модель использования node.js — та же: «принеси-подай» из базы
Да-да, в простонародье — формошлепство (а точнее, его бекенд). Почему же тогда фанаты JS начинают брызгать слюной, когда им говорят что их язык не годится для чего-то более серьезного? (ну, или, более толерантно, подходит хуже чем другие промышленные языки).

Я вот не говорю, еще раз, что многопоточность плохо (или хорошо). Я говорю о том, что рассуждать о ней надо в контексте конкретной системы.
Вообще-то в прошлых тредах (да и в этих тоже) фанаты утверждали, что многопоточность не нужна вообще, потому что слишком сложно. А это подразумевает как раз отсутствие контекста. За что их и высмеивали.
чего-то более серьезного

Пример вам под руку: написать GET-метод, который полезет в БД, достанет оттуда данные, соберет из них excel-файл и отправит клиенту. Вот тебе бабушка и IO-bound задачи юрьев день

Самое печальное, что так ведь и делают: крутят в одном потоке как генерацию xls, так и асинхронный чатик. И потом еще не верят что чатик зависает. У нас же NodeJS, это невозможно! Но если что, виноваты конечно же программисты, а не экосистема, которая их воспитала.

Вот мы и нашли тонкую грань между хеллоуворлдом и серьезным проектом. Если для всей необходимой функциональности достаточно IO-bound операций (сериализация/десериализация не в счет), то вы пишете хеллоуворлд.

Ну, проблемы генерации больших excel-файлов на количестве потоков не заканчиваются. Так что тут node.js и C# для меня одинаковы — в обоих случаях я предпочту вынести генерацию файла в отдельный процесс :-)

Генерация-то ладно, она обычно не раздувается по памяти и CPU до бесконечности. А вот парсинг — может, как уже не раз показывал опыт с Apache POI, PDFBox и Tika.

Не скажите. Мне вот в этом году пришлось фиксить багу вида "после выгрузки в Excel справочника на 100000 строк сервер перестает отвечать". Правда, эта ошибка воспроизводилась только в тестовой среде, где веб-сервер и сервер СУБД за память воюют — но в другой процесс генерацию я все равно в итоге вынес.

Я повторюсь. Обработку картинок нейросетью на сервере я не буду писать на node.js. То что называют «формошлепством» — далековато от истины в случае того же loopback, который можно настроить на абсолютно всё.

Я противник лишней работы на сервере, и считаю, что для большинства современных задач (работаю в немаленькой веб-студии), скажем > 80%, node.js более чем достаточно.
что для большинства современных задач (работаю в немаленькой веб-студии)

Наверное, вы хотели сказать "для большинства задач веб-студии".

Мы в каких-то видимо сильно разных мирах живём, но как мне видится пласт задач вида "сходи в базу, принеси A, потом прими решение на его основе и принеси Б" — довольно широкий и охватывает если не весь веб, то довольно серьезную его часть.


Разумеется, есть еще уйма CPU-bound задач, которые на ноду ложатся мягко говоря не очень. Но это ведь не делает ноду плохим инструментом, для решения io-bound задач.


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

Вот статья, рассказывающая о том, как работает nginx. Суть её — вся магия работает покуда у вас только io-bound операции. А nginx как раз для таких и предназначен (раздача файлов, статический контент). А вот другая, не менее объемная статья о том, как сложно nginx-у жить с cpu-bound операциями и как он вынужден с ними справляться. Повторяю еще раз. Насущная CPU-bound операция для бизнеса, например — генерация документов (тот же самый excel). И покуда остаются проекты с cpu-bound операциями (а почти все серьезные проекты именно таковы) путь для nodejs в мир серьезного продакшена закрыт.

Ну с чего бы закрыт-то? Просто надо в отдельные потоки или процессы такие операции выносить.

А это уже многопоточность. Это уже, как выше было JS-разработчиками сказано, сложно. А сложность, как они же нам объяснили — не нужна :)

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

И через что будет строиться этот обмен сообщениями? Shared memory? Добро пожаловать в мир всего того, что вы сказали. Пайпы? Попрощайтесь с кроссплатформенностью. Сетевые сокеты? Ну вариант, но как по мне — серверное приложение, которое подключается само к себе выглядит как минимум странно.


Плюс к тому же — если я правильно врубаюсь, то nodejs как раз активно пытается уйти от излишних потоков в пользу архитектуры а-ля nginx. Однако во-первых тредпул она уже активно использует, во-вторых… будет еще один? :) Так или иначе разработчикам придется управлять потоками. А они, как сами дают понять — не умеют.

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

Ну фиииг знает. Я слабо представляю себе nodejs-разработчика, который без опыта классического мультитрединга будет строить IPC на сообщениях.

А зачем нужен сложный обмен сообщениями, чтобы построить xls в отдельном потоке? Запустили процесс и ждем ответа. Я бы сделал как-то так:


const exec = require('execa');

app.get('/get-xls', async (req, res) => {
   const params = getParamsFromRequest(req);
   const data = await db.requestData(params);

   const {stdout} = await exec('node', ['./generate-xls', data]);
   res.send(stdout);
});

Как-то странно вы данные через командную строку передали. Они же структурированные и их много. Да и итоговую xls в памяти держать — тоже неправильно. И обработка ошибок нужна...

Данные предполагалось передавать чем-то типа json в виде аргумента командной строки. Все равно любые сообщения между процессами это строки, так что сериализовывать придется.


А если не нравится загружать весь xls в память, то можно взять stdout как поток и через pipe завернуть его в response.


Получилось бы больше кода, но не страшно.

В файл её и отдать через sendFile

Я думал об этом варианте, в памяти с данными работать быстрее, чем писать на диск. (Мы же не делаем XLS-таблицы на 8 Гб?)


В любом случае, варианты есть, а утверждение автора pnovikov, что в node.js все плохо с многоточностью/многопроцессностью — неверно

Да варианты-то есть всегда. Просто, например, в C#/MVC генерация xls (cpu-bound) не является проблемой вообще и никаких "вариантов" для неё не надо — берешь и пишешь (с асинками разумеется)

2 гигабайта в последнем поколении сборщик мусора не собирает пока его не пнуть. А после пинка — собирает довольно долго...


Я бы не говорил что в C#/MVC генерация xls не является проблемой вообще.

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

Верно, совсем забыл про stdin. C ним будет намного лучше!

Так message-based IPC в event-driven языке с managed memory и async/await искоробки, это так же просто и прямо как котят гладить.

Это Erlang. Ну или C# с Akka. Но JS-разработчики так не хотят. Они хотят Javascript :)

Пайпы? Попрощайтесь с кроссплатформенностью.

Эмгм. Что у QLocalSocket, что у System.IO.Pipes проблем с кроссплатформенностью не наблюдал. Причём у второго не наблюдал даже во времена Mono 2.6.


Просьба пояснить, что имелось ввиду.

После прочтения статьи я немного удивился: ни в Windows, ни в Linux нет полноценной возможности асинхронно читать файлы.

Постойте, я и не говорю, что нода хороша для CPU-bound операций. Больших фишек у ноды ровно две:


  1. асинхронный io с приятным интерфейсом для простых смертных (нет, последователи Карри, для этого не нужна специальная монада, и да, парни из мира Стандартных Изданий, для этого не нужно упражняться с тредпуллами), который внезапно показывает весьма приятную производительности без лишнего тюнинга.
  2. javascript. Который, внезапно, не смотря на все фу-фу-фу, доступен практически любому разработчику на Java, C, C++, C#, Pascal, PHP итд. А если немного подтянуть матчать и разобраться в том как работает this и прототипное наследование (которое, вообще говоря надмножество Java-like OOP), то внезапно оказывается, что это вполне пристойный инструмент. А если копнуть еще чуть глубже, то внезапно оказывается, что JS можно декорировать типами (flow), проверять линтерами, собирать под платформы разной степени древности (babel) и использовать фичи из черновиков грядущих стандартов прямо вот сейчас (опять, привет babel). Чем, не смотря на некоторую костыльность и соберисамность, может похвастаться далеко не каждый ЯП.

Теперь про CPU-bound и тот-самый excel. Я конечно в офисных xml делах не очень рублю, но что-то мне подсказывает, что если мы собираемся массово генерировать excel- файлики, то я бы предпочёл это действо передавать системе и вызывать отдельным процессом. Потому что наверняка есть хорошая сторонняя/системная тулза, которую не стоит пытаться запихнуть ни в ноду, ни в похапэ, ни в питон.


Это не значит, что надо писать на ноде всё и вся. Но для типовых веб-серверных задач нода — прекрасный, доступный и достаточно мощный инструмент.

Я бы взял какой-нибудь js-xslx и генерил бы файлики на клиенте. Если нужен один файлик на много скачиваний… да, придется делать его на сервере (в таких случаях обычно это редкий запрос). Можно дернуть процесс (опять хоть с тем же js-xslx), хотя в доках по js-xlsx виден метод writeFileAsync (с коллбэком).
Я бы взял какой-нибудь js-xslx и генерил бы файлики на клиенте

Хватит! Люди. Не везде и не у каждого суперкомпьютер. Тестируете своё приложение на своих машинах с 16 гб памяти и core i7. И думаете что всё в порядке. Любой чих перекладываете на клиент, что бы не дай бог сервер не загрустил… Вы там на адруино приложения хостите что ли?! Генерировать файл на клиенте…

А в чем проблема? Eсли речь идет не о 8гб таблиц, то до определенного предела фронтенд вполне себе в состоянии из json получить xml.


Разумеется, если киллер-фича приложения это генерирование xlsx на любой кофеварке с браузером, то нужно крепко подумать, желательно дважды. Но если речь идёт о данных, которые уже есть в вебе и их не десятки мегабайт, то в чем проблема? Современные движки JS достаточно быстрые для этого.

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

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


Если рендеринг является бутылочным горлышком — распараллельте его. Все равно процессорное время, затрачиваемое БД на обработку запросов, много больше времени рендеринга.


Почему-то Facebook, ВКонтакте до сих пор используют PHP. Можете это пояснить?


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

На самом деле, асинхронность — та же самая разновидность многопоточности. Принципиальная разница здесь заключается только в том, что в одном случае планировщиком задач является сама операционная система, а в другом — пользовательское приложение. И в случае ручного переключения задач мы вполне можем добиться экономии на многих вещах: на переключении контекстов, на примитивах синхронизации.


Кстати, что-то подобное было в Windows 3.1, когда приложения (процессы?) работали в многозадачном режиме, но приложения в явном виде сообщали системе (DoEvents), что они готовы передать управление в другое приложение.

Асинхронность говорит лишь о порядке выполнения, который не важен, в отличие от синхронного, и больше ни о чем (о разновидностях многозадачности упоминать еще рано). Но это, вроде бы простое, свойство открывает ряд интересных возможностей для применения различных стратегий выполнения такого кода, что будет принципиальным образом сказываться на производительности и отзывчивости.

Вставлю 5 копеек внеапно, в защиту JS.


рендеринг на стороне клиента — просто попытка оправдания использования новомодных технологий

Да нет, вы знаете, зачастую бывает очень уместно переложить монотонную конкатенацию строк на клиентскую машину. Тут, конечно, важно не перегибать, но в общем и целом рендеринг HTML-я, если так, по-хорошему — это не очень релевантная для сервера задача. Чисто концептуально, если её можно перекинуть на клиента, который бьет баклуши — я лично, с позиции бекенда, был бы только за. Другое дело что встают вопросы с индексированием… Но это уже к самой концепции отношения не имеет.


Если рендеринг является бутылочным горлышком — распараллельте его

Рендеринг не параллелится в общем случае. Максимум что можно — сделать его поточным. Чтобы соединить пайплайн отрисовки и записи в сокет. Но и тут все сложно.

UFO just landed and posted this here

Думаю что backend на JS — это очень плохая идея. Поэтому изоморфизм в общем случае не взлетит, покуда у нас нет технологии для изоморфного рендеринга не на JS.

UFO just landed and posted this here

Простите великодушно, у меня чуть другой взгляд на проблему — я уже почти собрал движок для изоморфного рендеринга на C#. :)

UFO just landed and posted this here

Что-то вроде. .cshtml-шаблон с некоторыми специальными расширениями за счет хитрых перегрузок TextReader-а, при запросе выдает не HTML, а специальную рендер-функцию, которую можно через шаблонизатор вызвать на клиенте и она вернет строку с HTML-ем, которая пропускается через поточный HTML-парсер на state-машине и через атрибуты обратной привязки связывается с вью-моделью. Можно делать VDOM-перерисовку (сейчас вот запиливаю, о чем сказал в статье). Тот же .cshtml-шаблон технически можно выполнить и на сервере, получив тот же результат.


Я все хочу выдрать эту фиговину в отдельную библиотеку и присобачить к ней какой-нибудь транслятор из C# в JS, распарсив существующие ts-тайпинги в C#-интерфейсы. Просто чтобы посмотреть что получится.

UFO just landed and posted this here

Ну вам же не нужно перепиливать существующие скрипты и компоненты, когда вы пишете на TypeScript. :)


Я эту идею вынашиваю уже давно и по задумке это получается очень похоже на React.js, только не на JS, а на C#. Помимо этого возможна прозрачная интеграция с ASP.NET MVC-бекендом. Т.е. вы можете вот те же интерфейсы REST-контроллеров использовать тут же, в клиентском коде, что сделает, например, swagger идейно бесполезным для таких проектов.

UFO just landed and posted this here

Ну там конструкция построена так, что кто рендерит основную часть верстки — не важно. Главное чтобы в ней были атрибуты обратной привязки. На любой HTML-элементы вы можете добавить специальный backbind-атрибут @Callback("initPlugin"), который вызовет абсолютно любую JS-функцию после отрисовки элемента — и этот элемент функция получит первым параметром. Так же на вашу совесть ложиться добавить @DestroyCallback("killPlugin"), который вызовется при удалении элемента из DOM-дерева. Если ваш коллбек добавляет какие-либо дополнительные ноды — то лучше обернуть это место в div с атрибутом @VDomDontTouch(), чтобы VDOM-перерисовка не корежила ваши jQuery-плагины (спасибо bano-notit за эту идею). Это как оно работает сейчас. В случае если использовать транслятор из C# в JS, то аргументы @Callback и @DestroyCallback скорее всего будут переписаны на строго типизированные (лямбда-выражения).

А рассматривали опыт аналогичных предшественников (gwt, например или jsjinja)? Предыдущие попытки пока заканчивались не очень из-за тормозов при выполнении, проблем с безопасностью и с отладкой генерированного кода.

Вообще основные заморочки с JavaScript ведь не в языке, а в большом количестве разнообразных неконтролируемых сред исполнения. Это не сразу бывает очевидно при переходе с бекенда. Каждый браузерный движок имеет свои особенности, которые необходимо учитывать.

Например, первое, что приходит на ум. На бекенде, в обычной среде исполнения ASP.NET MVC, дернуть из chtml метод, выполняющий запрос в базу, можно счесть и за шалость. При передаче такой имплементации на клиента, даже если решены все технические проблемы, это может стать дырой в безопасности. Аналогично получается со всеми сторонними зависимостями, библиотеками и т.п.

Поэтому писать в такой системе все равно придется с постоянной оглядкой на клиент-сайд, т.е. еще на любимом языке, но «как бы на JavaScript». Все бы ни чего, но это на порядок усложняет (удорожает) как задачу самого кодинга, так и последующей поддержки.

Не волнуйтесь, я знаю что делаю :)

А можете рассказать каким образом это решается в вашем фреймворке, и какие накладываются ограничения?

В каком фреймворке, простите? Это пока еще только задумка — у меня на руках только изоморфный шаблонизатор.


Что касается "дернуть метод из cshtml" — в настоящий момент этот метод выполнится на сервере и его результат запишется в клиентский шаблон (да, параметризуемый шаблон для шаблона — так тоже бывает). Методы, которые влияют на генерируемую шаблон-функцию и нативные C#-конструкции строго друг от друга отделены. В случае если у кого-то чешутся руки пошалить — кидается ошибка компиляции.

Спасибо, теперь слежу.

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

Согласен, но из соображений экономии трафика, а не экономии производительности. Конкатенация строк — это быстрое действие.


Чисто концептуально, если её можно перекинуть на клиента, который бьет баклуши

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


Рендеринг не параллелится в общем случае. Максимум что можно — сделать его поточным. Чтобы соединить пайплайн отрисовки и записи в сокет.

Как это? Почему клиенская машина может рендерить страницу независимо от сервера, но сервер этого делать в общем случае не может?


Но и тут все сложно.

Подумайте о старых технологиях, где это было совсем просто.

Мы видимо разные вещи подразумеваем под словом "рендеринг". Я имею в виду формирование HTML для страницы. И в современных условиях, мне кажется, клиент вполне в состоянии сделать это сам, имея нужные данные. Серверу конечно не сложно, но идеологически это не его задача, хотя де-факто много лет он её выполнял. Под тем, что эта задача не параллелится я имею в виду, что невозможно ускорить формирование HTML для страницы с помощью нескольких потоков. Тем более, как показывает лично мой опыт, сформировать большую строку (и даже её распарсить) — это пустяки по сравнению с отрисовкой. И раз уж клиенту все равно её рисовать — то пущай и формирует. Нече сервер по пустякам дергать :) "Вас много, а я одна", "у меня таких как вы тысячи" и все такое.

Мы видимо разные вещи подразумеваем под словом "рендеринг". Я имею в виду формирование HTML для страницы.

Я тоже.


И в современных условиях, мне кажется, клиент вполне в состоянии сделать это сам, имея нужные данные.

Конечно, в состоянии. Но у клиента только браузер с JS. А на сервере HTML может формироваться гораздо более эффективными способами.


Под тем, что эта задача не параллелится я имею в виду, что невозможно ускорить формирование HTML для страницы с помощью нескольких потоков.

Но это и не нужно ускорять. Тем более, что это пустяки по сравнению с отрисовкой.


И раз уж клиенту все равно её рисовать — то пущай и формирует. Нече сервер по пустякам дергать :) "Вас много, а я одна", "у меня таких как вы тысячи" и все такое.

Если клиент получает от сервера HTML с минимумом скриптов — отображение такой страницы для клиента — лёгкая задача.


Если клиент получает от сервера JS-бандл + данные, причём скрипт однократно генерирует HTML при каждой загрузке/обновлении страницы — это тоже нормально. Один раз протормозится, дальше будет хорошо. Огромный минус — тормозить будет при открытии каждой новой вкладки.


Но если скрипт работает на уровне DOM (типа, так удобнее программистам) — клиента ожидает лагалово, особенно, когда данных много.

А на сервере HTML может формироваться гораздо более эффективными способами.

Если в двух словах, то я просто отдаю клиенту JS-функцию, которая принимает на вход модель и возвращает строку с HTML. И эта функция генерируется на сервере (разумеется, результат легко кэшируется), в неё переносятся все эффективные способы формирования HTML. Читайте — шаблон компилируется на сервере. Клиенту остается просто вызывать — и вуаля. HTML есть. Дальше — хоть через innerHTML вставляй.


причём скрипт однократно генерирует HTML

Таки я не понял — мы предположили что получить HTML — это сложно или это просто? :) Клиенту так и так нужно будет преобразовывать полученный HTML в DOM-дерево и если мы предполагаем что шаблон-функция все-таки простая и не завешивает его на 10 секунд (даже мне с моими кривыми руками не удалось добиться такой просадки производительности, хотя я пытался) — то разница в итоге получается небольшая. А бандл, кстати, можно и кэшировать.

Если в двух словах, то я просто отдаю клиенту JS-функцию, которая принимает на вход модель и возвращает строку с HTML.

И это хорошо.


Клиенту так и так нужно будет преобразовывать полученный HTML в DOM-дерево

Разница в том, что браузер HTML в DOM преобразовывает быстро.
А вот формирование DOM напрямую, используя JS — это медленная задача.


А бандл, кстати, можно и кэшировать.

Но браузер все равно будет тратить сколько-то времени просто на его парсинг.


P.S. Я JS-программист уровня 0, и мой взгляд — чисто со стороны пользователя: просто не люблю, когда сайты тормозят.

Аээ… DOM можно сформировать только в браузере. Нельзя в браузер "отдать DOM" — узлы привязаны к контексту и единственный способ их сериализации — HTML (да и то без привязанных событий). Таким образом единственный путь — сделать чтобы HTML появился в браузере любым способом, а дальше создать DOM-узлы или через innerHTML, или через самописный парсер.

Разница в том, что браузер HTML в DOM преобразовывает быстро.
А вот формирование DOM напрямую, используя JS — это медленная задача.

А можно пруф?

Ой, а можно я за него отвечу? :)


На самом деле короче нет. innerHTML чуть быстрее чем document.createElement и они оба даже работают чуть быстрее, чем изначальный парсеринг/рендеринг HTML-документа при загрузке страницы. Источник — личный опыт.

Мой личный опыт говорит обратное. Я даже писал бенчмарки (но не сохранил). Потому и прошу пруф.

Там сильно зависит от браузера на самом деле. Через innerHTML можно отрисовать в общем случае не все, поэтому треба использовать только document.createElement (в общем случае). Например в IE7 вы не можете проставить innerHTML у tbody (внезапно). Поэтому только через парсер и createElement. И да — оно работает СУЩЕСТВЕННО медленнее чем в IE9 и Edge. В хроме вроде проблем с производительностью не возникает. Но с таблицами тоже не все работает.

Еще есть метод insertAdjacentHTML, он вроде как быстрее установки свойства innerHTML работает.

А можно пруф?

Ну вот, например. Правда, там используется jQuery. Если использовать VanillaJS, то результаты будут другие: в старых браузерах генерация HTML была более быстрым вариантом, в современных — всё наоборот. Видимо, я не был в курсе последних оптимизаций браузеров.

Тут еще смотрите какой момент. Если вы выведете на страницу, скажем, 70 тысяч элементов (я выводил 10 тысяч строк в таблице по 7 колонок, без доп. форматирования), то у вас все будет тормозить вне зависимости от скорости генерации HTML и создания dom-узлов. Ну просто потому что браузеру сложно отрисовать такой объем данных. Ну и как правило, пользователям видеть столько данных одновременно — тоже не с руки. Там уже вступают в игру всякие виртуализации, пейджинги и прочее разделение интерфейса на части. Так что практической пользы в создании огромного числа элементов нет — они все равно не смогут отрисоваться как надо. А небольшое число элементов в любом случае инстанциируется довольно быстро. Так что мерить производительность document.createElement тут не совсем релевантно.

Ну зачем сразу 10 тысяч? Сколько-то там лет назад при выводе совсем простой таблички я наблюдал подтормаживание уже при 50 строчках с использованием JQuery, без же него нормально было только до 300-400 строчек.

Межпрочим, мой Lattice и был основан сначала на jQuery. Потом, когда я познал быстродействие на 100 строчках — мне пришлось сделать свой HTML-парсер

По вашей первой ссылке сравниваются разные способы использования innerHTML, а не работа со строковым HTML против DOM.

Ведь все пользуются однопоточным nginx для статики, не так ли? Так вот. Всем же очевидны его преимущества?

https://nginx.ru/en/docs/ngx_core_module.html#worker_processes:


worker_processes number | auto

https://nginx.ru/en/docs/ngx_core_module.html#thread_pool:


thread_pool name threads=number [max_queue=number];

Как минимум в linux процессы и потоки технически отличаются не столь сильно.

Так ведь и в ноде есть cluster. Слово "однопоточный" в данном случае указывает не на количество потоков, а на модель работы с сетью, когда сокеты распределяются по существующим потокам вместо создания нового потока на каждый сокет.

Т. е. если я использую thread pool, скажем, в jetty, то программа/среда выполнения внезапно стала однопоточной о_О?

На Луркморе упомянуты и каталагизированны_усе :)
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Первая статья из этого цикла "JavaScript как..." была написана как раз-таки функциональщиком, отрицающим такие индустриально-базовые вещи, как return и скобочки.

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

"Так что же вы стоите, батенька?! Давайте к нам, на бrоневичок, на бrоневичок!"

И глаза такие добрые-добрые..

А ведь мог и TypeScript-ом полоснуть.

Всегда приятно, когда люди опознают классику ,)

Пост как раз об этом. В шапке же написано, что я критикую фанатиков, а не технологию


Так нужно было и писать о фанатиках, а не о технологии.
Объясните, разве это плохо — ездить на конференции по своему языку программирования? Плохо, что в какой то технологии можно за 20 секунд сделать что-то?

Нет, ездить на конфернцию не плохо.

Плохо терять критическое мышление в результате этой конференции)))
именно об этом пишет автор.

в который раз раз повторюсь: я был свидетелем безумия рубистов и пхпшников, которые запихивали свои «супертехнологии» в промышленные проекты ( со сложной бизнес-логикой, высокими требованиями к надежности, и большим объемом обратываемых в одном запросе данных, с высоким темпом изменений, в условиях большого объема кода и сложной логики) — не понимая где лежит граница применимости их технологий.
и я видел, что происходило с этими проектами через полгода, через год, через 2. ничего хорошего.

ведь, имхо, именно о потере критического мышления пишет автор во фразе «вирус откусывает ту часть мозга, которая помнит о многопоточности — она умирает не успев задать свои вопросы».

событийно ориентированный стиль разработки прекрасен.
но строго до тех пор, пока им не пытаются замень что то сугубо многопоточное или требующее синхронности.
скажите, вы когда либо писали код взаимодействия с промышленым принтером через rs-232? а я писал.
и в гробу я видел, извините за выражение, тех, кто написал «событийно ориентированные» классы, которыми я был вынужден пользоваться. потому что когда ты работаешь с принтером — тебе надо поддерживать его состояние и контекст, а проверять это всё каждый раз когда ты просыпаешься в «onByteRecieved()» — настолько усложныет код, что мне пришлось отдельно сидеть и писать «синхронные обертки» над асинхронным интерфейсом работы с ком-портом. потому что только синхронный интерфейс позволял мне сохранить простоту структуры кода и прозрачность логики. а обертка работала, только благодаря многопоточности.

вот такая сказка об идеализации однопоточности и событийности в реалиях жизни.

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

«Здоровый» человек не будет обсирать другую технологию, которую он не понимает, которая стала вдруг популярной. «Здоровый» человек напишет про свою любимую технологию.
Не хочешь JavaScript на backend, не применяй (если ты решаешь чему быть), убеди тех. директора чем C# лучше JavaScript, не работай в компании с «больными» людьми, и все остальное в этом духе.

несколько не правы, имхо. действительно здоровый человек знает ограничения и границы прменимости технологии. и использует js там где он пригоден, а java там, где java работает лучше всего, с# — там где ему место, а не убеждает тех директора что c#/java лучше или хуже js.

у каждой технологии — свои границы применимости. и, напрмер, имхо, пихать js-на сервер, это прмерно так же, как java-апплеты встраивать на страницы публичного сайта. в принципе, работает, но «здоровые» люди так как правило не делают, потому что слишком много минусов, и ограничений у получившегося решения.
событийно ориентированный стиль разработки прекрасен.

Ну, к слову, async/await в JS позволяют весьма неплохо причесать код, избавив от необходимость делать цепочку колбэков, замыканий и хранений состояний.


Касательно работы с ком-портами в C# — да, это недоразумение. Я предпочитал работать напрямую через WinAPI.

Работал с COM-портами в шарпе, никаких проблем не видел. Ну, там конечно довольно старый АПИ на EAP, вместо тасок, но в целом довольно адекватно.

так, народ)))
во первых… не в шарпах я работал. дело было в году 2006-м 2007 и сишарп тогда был… далеко не самым интересным, чем можно было заниматься. от слова совсем.

во вторых — я делал на с++ кроссплатформенную обертку над работой с компортами, и в виндовой части проекта я работал с WinAPI и использовал неблокирующее чтение с коллбеком. Потому что реализовать блокирующее чтение с таймаутом — … ну вот как-то не вышло. В линуховой части все вышло на ура, а вот c WinAPI — матов было много. А таймаут надо было иметь.

а потом туда же (через этот интерфейс) был пристыкован доступ к «виртуальному компорту» ( serial профиль USB-устройства — некоторые принтеры делают именно так)… и матов было примерно столько же.

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

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

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

нет))) не правильнее))
не будет а-синхронный интерфейс давать вам видеть нормально логику работы кода и отслеждивать диалог с принтером.

Мыслите не уровнем «stateless» механизма принял-отдел, а уровнем всей задачи, когда вам надо хранить состояние и (очень желательно) контекст исполнения.

Вот давайте по порядку на примере

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

Рассмотрим на примере псевдо c++ кода гипотетической функции инициализации принтеров определенной серии с использованием синхронных функций:

Функция waitForBytes() — синхронная. не отдает управление пока не получен минимум 2 байта, или не истек таймаут. Если получено больше чем ожидается — отдает весь полученный объем данных.

int initPrinter()
{
serial->send( {0x??, 0x??, 0x??}); //запросим модель принтера
byte[] resp=serial->waitForBytes( 18, 5000); //ждем ответ принтера - минимум 8 байт, 5 секунд.
if (resp==NULL) return -100; // timeout
if (sizeof resp <8 ) return -101; // ответ не распознан
String model = new String(resp);
if ( ! model->startsFrom("Posiflex") ) return -102; // не поддерживаемая модель принтера
if ( ! model->equals("Posiflex PP5200") ) 
  {
     serial->send( {0x??, 0x??, 0x??}); //запросим прошивку принтера
     resp=serial->waitForBytes( 2, 5000); //ждем ответ принтера - 2 байта, 5 секунд.
     if (resp==NULL) return -100; // timeout 
     ...
     if (resp[0]==0x23 and resp[0]==0x24) // упс... у нас тут модель с прошивкой с известным багом
      {     
          serial->send( {0x??, 0x??, 0x??}); //уст. кодировку 866 - в этой прошивке исходно 1251
          resp=serial->waitForBytes( 2, 5000); //ждем ответ принтера - 2 байта, 5 секунд.
          if (resp==NULL) return -100; // timeout 
           ....
     }
  };
serial->send( {0x??, 0x??, 0x??}); //переведем принтер в страничный режим с абс. позиционированием
resp=serial->waitForBytes( 2, 5000); //ждем ответ принтера - 2 байта, 5 секунд.
if (resp==NULL) return -100; // timeout 
  ....
return 0;
}

Вот теперь, перепишите этот маленький и простой кусочек кода «в асинхронной, неблокирующей логике». Вы сможете сделать его столько же простым, прозрачным и понятным, что бы он не вызывал проблем при отладке, и не приводил к желанию материться?

Поймите, тут как раз тот случай, когда асинхронная логика «ну не в вперласть» никаким боком.
От слова совсем.
Удобно и просто прозрачно когда один поток, который ведет диалог с принтером, и ждет ответ от него, с сохранением контекста исполнения и состояния. Потому что это позволяет просто и легко описывать сложную логику диалога с устройством.

Если вам сложно мыслить принтером и последовательным компортом, то представьте, что у вас сокет на удаленный хост — у вас кастомный протокол, и в процессе «рукопожатия» — вам надо запросить у него кучу подробнотей, и в зависимости от того, что он вам ответит — что то ему рассказать дополнительно, или не рассказать.

В языках, которые умеют async/await (тот же C#), в вашем примере все сведется к такой замене:


serial->waitForBytes( 18, 5000)

на


await serial->waitForBytesAsync(18, 5000)

В результате код ничуть не усложнился, а взаимодействие стало асинхронным. Так что асинхронные интерфейсы при наличии соответствующих механизмов ничуть не сложнее синхронных.

Скажите, а вы точно понимаете что такое асинхронный и синхронный вызов?

Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции, которая заявлена как «асинронная», и говорите что взаимодействие стало асинхронным? вы уверены что не запутались?

Синхронный вызов — это когда исполнение приостанавливается в точке вызова.
Именно это вы и делаете, когда предлагаете использовать await.

А блокируется или приостанавливается при этом поток, какие потоки при этом вызываются, что используется внутри или не вызываются, что происходит внутри — это уже технические детали рализации.

Например похожие технические эффекты, (похожие на await) можно добиться в Java 'без всяких await' с использвоанием yeld() — он отдает ресурсы потока на другие задачи, не не разрушая контекст исполнения.

А «асинхронный» вызов, не приостанавливает выполнение кода в точке вызова в ожидании ответа, а сразу преходит к следующей инструкции.
Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции.
Синхронный вызов — это когда исполнение приостанавливается в точке вызова.
Именно это вы и делаете, когда предлагаете использовать await.

Синхронный — это если бы я написал waitForBytesAsync.Wait().
Здесь же, если говорить грубо, await приводит не к синхронному ожиданию, а просто код после него будет вызван, когда асинхронная операция завершится. В остальное же время поток будет свободен и ничего ждать не будет.

https://en.wikipedia.org/wiki/Asynchrony_(computer_programming)


await — оператор синхронизации места его использования в зависимой задаче с завершением асинхронно запущенной задачи. В сумме получается вполне себе синхронное исполнение. То, что блокируется не системная "нить", а легковесное "волокно", сути не меняет.

То, что блокируется не системная "нить", а легковесное "волокно", сути не меняет

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

Системные нити точно так же "выходят", а потом "возвращаются". Единственное, что отличает explicit реализацию сопрограмм (которой нужны async/await) от implicit (которой они не нужны) — это то, что первые не имеют стека, а вторые — имеют.

Куда выходят? Когда? Куда возвращаются? Причем здесь стек? Причем здесь вообще потоки ("нити"), если async/await не завязан на них и будет спокойно работать в среде, где понятия потоков может вообще не быть?

Ваши async functions — частный случай coroutines, которые также известны как lightweight threads. Как вы думаете почему?

Нет. Coroutines — это кооперативная многозадачность с планировщиком, работающим в пользовательском коде. Нити имеют свой стек, и переключение между ними производится явно.


А асинхронные функции — просто функции, которые компилируются в state machine возвращают Task<>. Просто синтаксический сахар для событийно-ориентированного программирования, и ничего больше. Планировщик может задачи выполнять как кооперативно, так и параллельно, в зависимости от реализации. Если, например, нет ни IOCP, ни epoll/kqueue, то ему ничего не останется, как запускать задачи параллельно.

Асинхронные функции — не более чем stackless coroutines.


Что thread, что stackfull coroutine, что stackless coroutine имеют пачку кода, расположенную последовательно и указатель на текущий кусок из этой пачки. Логически это сущности одного порядка. stackfull имеет бонусом стек, stackless — вырожденный случай stackfull с фиксированным размером стека = 1. Вот и всё различие. Исполняться они все могут хоть на одном ядре, хоть на нескольких, хоть по очереди, хоть попеременно — это уже во власти планировщика и расставленных yield и await.

А какое отношение это имеет:


  1. к вопросу о том, как связаны понятия потоков (thread, нить) и async/await?
  2. к вашему изначальному утверждению, что от использования await исполнение внезапно становится синхронным? Я так и не увидел аргументов к этому утверждению.

Код синхронный, когда исполнение идёт последовательно с ожиданием завершения подзадач. Используете ли вы для синхронизации sync call, await или thread.join — совершенно не важно.


Смотрите, совершенно синхронный код, который при этом исполняется на всех ядрах процессора:


enum n = 1_000_000;
enum delta = 1.0 / n;

alias getTerm = (int i)
{
    immutable x = ( i - 0.5 ) * delta;
    return delta / ( 1.0 + x * x ) ;
};

immutable pi = 4.0 * taskPool.reduce!q{ a + b }( n.iota.map!getTerm );

assert(pi.approxEqual(3.1415926));
Код синхронный, когда исполнение идёт последовательно с ожиданием завершения подзадач.

Видимо, ваш "синхронный код" — это вообще не то, что синхронные/асинхронные операции в общепринятом понимании. Приведите пример "асинхронного кода" в вашем понимании.


И по-вашему, если я начал долгую асинхронную операцию IO, потом долго что-то считал, и потом "жду" результата начатой ранее операции через await — это типа все синхронно?

потом "жду" результата

Это и называется "синхронизация". Без неё ваш код становится асинхронным.

Неужели? То есть, по вашему, первый код синхронный, а второй нет?


// 1
async Task Example1()
{
    await DoSomethingAsync();
    DoSomethingElse();
}

// 2
Task Example2()
{
    var t1 = DoSomethingAsync();
    return t1.ContinueWith(t => DoSomethingElse());
}

И не уходите от вопроса. Что в вашем понимании асинхронный код?


Вообще асинхронность подразумевает, что что-то происходит независимо от основного потока выполнения. Использование await соответствует этому определению, потому что фактически обозначает:
То, что после await, выполнится тогда, когда задача, на которой сделан await, завершится (что является асинхронным событием). А сейчас возвращаем управление тому, кто нас вызвал.

Да, во втором случае, когда мы выходим из функции колбэк ещё не отработал.

Сюрприз, в первом тоже. При вызове await происходит выход с незавершенной задачей. Это один и тот же код, написанный по разному.

Классный сюрприз — забыл await и всё сломалось.

Классный сюрприз — забыл if и все сломалось.

Поэтому ниже вы предлагаете вообще отказаться от async/await. Тогда-то ошибки не будет, просто сломается и починить нормально нельзя будет.


Вы уходите от первоначального вопроса: о том, что это один и тот же код и что он может быть в обоих случаях синхронен или асинхронен (в вашем понимании), но точно не в одном случае так, а в другом иначе.


И "забыл await" — это совершенно другой вопрос. Для этого есть инструментарий, который показывает предупреждения, для этого есть суффикс Async, который намекает на необходимость await, и все такое. Никто не мешает во втором примере вернуть t1, что будет точно такой же ошибкой.

Ложная уверенность — хуже её отсутствия.


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

Классный сюрприз — забыл await и всё сломалось.

Умная среда разработки и компилятор напомнят программисту о том, что он дурак — вызвал функцию и не использует её результат.

Вообще асинхронность подразумевает, что что-то происходит независимо от основного потока выполнения. Использование await соответствует этому определению, потому что фактически обозначает:
определение не полное, именно поэтому вы путаете многопоточность и асинхронность.

Асинхронность (в случае локального, выполняющегося на одной машине кода) как правило говорит о том, что вы умеете реагировать на события/сообщения/уведомления от «того, что происходит независимо от вас» — когда что-то стороннее начинает вас уведомлять — и у вас как правило есть «обработчики»/«подписки»/«хуки»/«коллбеки»… — которые будут вызваны когда событие произойдет).

А если вы останавливаетесь на текущей инструкции, до тех пор пока не выполнится что то независимое от вас — это синхронный код.
Вы сами синхронищируетесь с чем-то — вне зависимости от того, на что вы смотрите или за чем вы следите.

То, что после await, выполнится тогда, когда задача, на которой сделан await, завершится (что является асинхронным событием). А сейчас возвращаем управление тому, кто нас вызвал.

завершение процесса — не стоит называть асинхронным событием.
Вот если бы ваш таск присылал вам в случайные моменты времени сообщения, и вы на них реагировали — это было бы асинхронным событием.

А так — это поведение, сходное с обычным «семафором».
Сравните вашу фразу с фразой "то, что после «вызова mуSemaphore.release(), выполнится только тогда, когда сосдений поток освободит семафор».

Но семафоры — это обычная синхронизация потоков, а ни какая ни «асинхронность».

Хорошо, такой вопрос, майкрософт в своей документации говорят, что async-await механизм асинхронного программирования, вы же очевидно утверждаете обратное. Вопрос — как вы считаете, майкрософт нарочно вводит людей в заблуждение или они просто неграмотные?

документация майкрософт… это очень скользкая штука. двусмысленнее этого только рекламные объявления.

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

прочитайте внимательно содержимое, ищите признаки описывающие асинхронность, и судите не по заголовку а по содержимому…
например там нет ни слова про коллбеки, или событийность, нет ни слова про асинхронный запуск (коим можно конечно классифицировать запучк процедуры в параллеьном потоке, но в асинк… типа же нет потоков(?)...)

там есть некая «регистрация продолжения (»continuation") про которыую никто не знает что это именно аткое.

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

и только ради хайпа и «повышения продаж» они назвали его «асинхронным программированием». хотя это ни то ни другое.

в части документации — я за ними лет 20 наблюдаю и поверьте мне — они легко пойдут на подмену понятий и назовут «теплое» «мягким», если это повысит продажи, или поможет сформировать в головах своих разрабов некую «узкокалейность майкрософтвей», наплевав на то, что во всем остальном мире это именно «теплое».

узкокалейность мышления — это примерно как с 1с — но последним хоть простительна узкокалейность, потому что у них свои уникальные механизмы (кстати, очень грамотно методически продуманные), и они не создают путаницу в терминах — «регистры учета» вот есть только в 1с, а общепринятые термины импользуют именно так, как это принято везде. вот1 или вот2. это понимание точно такое же как скажем в js.

на мисте, кстати, очень правильная фраза есть:
1с не реализовала неявное преобразование обычного кода в асинхронный, переложив тяжесть реализации асинхронности на программиста.
.

а вот майкрософт, похоже… реализовало. но вот что?)))

То, что вы называете "асинхронным программированием" с легкой' руки майкрософта, никакое не асинхронное програмирование, а скорее «неявное преобразование» обычного/синхронного кода во что то иное при компиляции.

теоретически, байткод может быть и асинронным, но на этот счет в указанной документации нет ни каких иных признаков, кроме «самопровозглашения что это мол асинхронное»: нет ни слова про обработчики событий, ни коллбеки…

есть, повторюсь, некая «регистрация продолжения»(continuatiin)" но такого технического термина нет, и что имеется в виду — никто не знает, и додумки ваших коллег про то что это выкидывается в коллбек-функцию — пока не подтверждены ничем.

сейчас, снова повторюсь, я бы классифицировал await/async как некий мехапизм «отложенных заданий».

Но это никак не асинхронный механизм.

Потому что там даже «асинхронного запуска» (типа запустили, оно начало работать, а мы отвалились) нету — потому что и параллельности то нет: согласно опять же этой документации, асинк / await не требуют многпоточностиок значит он не выполняется параллельно. последнее кстати, очень подозрительно и смахивает на ошибку.

я конечно не знаю деталей этой магии, но фраза
Асинхронные методы не требуют многопоточности, поскольку асинхронный метод не выполняется в своем собственном потоке. Он выполняется в текущем контексте синхронизации и использует время в потоке, только когда метод активен
… wtf?! вы серьезно?)))

так в каком потоке выполняются асинк-методы, из какого потока они выбирает время, если многопоточности нет, а в своем потоке он не выполняется. есть тайный тайный поток, про который забыли рассказать в документации и который не входит в понятие многопоточности? (это как история с пользователем Администратором, который на самом деле не совсем администратор, и может не всё, а есть еще более «администраторный администратор»)

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

в общем я не знаю кто кого обманывает, но, как минимум, называть использование отложенных заданий, с хитрым «магическим» процессом исполнения — асинхронным программированием — это подмена понятий.
документация майкрософт… это очень скользкая штука. двусмысленнее этого только рекламные объявления.

Значит все-таки врут. Как и мозилла, и куча других языков с аналогичным функционалом. Просто заговор какой-то


например там нет ни слова про коллбеки, или событийность, нет ни слова про асинхронный запуск (коим можно конечно классифицировать запучк процедуры в параллеьном потоке, но в асинк… типа же нет потоков(?)...)

То есть асинхронность это что-то с коллбеками? :) Никогда не встречал такого определения. Наверное, все же бывает и по-другому.


там есть некая «регистрация продолжения (»continuation") про которыую никто не знает что это именно аткое.

Ну если вы не знаете, не надо выдавать свою неграмотность за проблему языка.


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

Получается вы все-таки поняли. Да, async/await это способ писать асинхронный код так, чтобы он выглядел и читался нормально, а не как лапша из коллбеков.


Потому что там даже «асинхронного запуска» (типа запустили, оно начало работать, а мы отвалились) нету — потому что и параллельности то нет: согласно опять же этой документации, асинк / await не требуют многпоточностиок значит он не выполняется параллельно. последнее кстати, очень подозрительно и смахивает на ошибку.

Наверное, потому что многопоточность и асинхронность это разные вещи, м?


сейчас, снова повторюсь, я бы классифицировал await/async как некий мехапизм «отложенных заданий».
Но это никак не асинхронный механизм.

асинхронность это и есть про "я не могу дать результат сейчас, подожди, когда будет готово, и зарегистрируй действие, которое должно в этот момент произойти". await это как раз про это.


так в каком потоке выполняются асинк-методы, из какого потока они выбирает время, если многопоточности нет, а в своем потоке он не выполняется. есть тайный тайный поток, про который забыли рассказать в документации и который не входит в понятие многопоточности? (это как история с пользователем Администратором, который на самом деле не совсем администратор, и может не всё, а есть еще более «администраторный администратор»)

Там понятно написано для человека в теме, что такое контекст и какое отношение он имеет к потокам.


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

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


в общем я не знаю кто кого обманывает, но, как минимум, называть использование отложенных заданий, с хитрым «магическим» процессом исполнения — асинхронным программированием — это подмена понятий.

Никакой магии нет, просто компилятор сам переписывает код на коллбеки, чтобы программист не парил себе мозг этими простынями и что компилятор превращает такой код, который просто и понятно читается человеком
image
В такой
image
Где начинается чехарда с обработкой ошибок, непонятно что где происходит и непонятным дебаг экспириенсом. Вот презенташка целиком.


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

по порядку ))
Значит все-таки врут. Как и мозилла, и куча других языков с аналогичным функционалом. в документации мозилла и в документации майкрософт одинакового только слово async.
Просто заговор какой-то

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

То есть асинхронность это что-то с коллбеками? :) Никогда не встречал такого определения. Наверное, все же бывает и по-другому.
асинхронность, это event-driven стиль построения системы. коллбеки — это наиболее частый способ указания на функцию обработчик.

у вас нет в коде событий и функции обработчика событий? значит ваш код — ни разу ни асинхронный, потому что он построен не по асинхронной логике.

то что появляется в результате неявных преобразований в байткоде при компиляции — дело совершенно третье (там действительно может быть полный аналог мозиловского асинка, но может и нет), и это попадает под тему замены логики работы при компиляции.

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

асинхронность это и есть про «я не могу дать результат сейчас, подожди, когда будет готово, и зарегистрируй действие, которое должно в этот момент произойти».

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

да, await волшебным образом не блокирует текущий поток, но это иные свойства, не связанные event-driven стилем разработки.

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

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

или у вас есть ссылка на теорию операционных систем или ещё что что? бложики «разных специалистов» — это конечно хорошо, но подмена понятий — это уже плохо.

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

на остальную «лапшу» отвечу попозже.
мозилла, в отличии от сишарпа, возвращает промис, в который вы подставляете функцию-обработчик, которая должны быть вызвана

И в чем же вы видите принципиальную разницу между методами then и ContinueWith?

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

Двойная буква ять. await в js — сахар над Promise#then, работающий поверх Promise (с автоматической конвертацией не-Promise в resolved promise. См. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await.


То, что в шарпе не отличается ничем, кроме имён классов и методов.

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

Во-первых, принципиальное отличие await от семафора — отсутствие блокировки. Во-вторых, "волшебным образом" он это делает потому, что это по сути и есть callback, о чем вам сто раз сказали. Но вы, видимо, знаете, как работает await, лучше, чем официальная документация и целая куча специалистов и экспертов.


действия выполняющиеся вне какого-либо потока — нонсенс, противоречит основам работы операционной системы

Вам бы Таненбаума почитать или Руссиновича. Некоторые действия выполняются ОС на таком уровне, где понятия потоков и процессов нет вообще. Это к слову. И есть, например, такая штука, как DMA, которой программно ставится задание что-то где-то переместить в памяти, и она потом это делает сама независимо от процессора, который в это время может делать что угодно другое. Каноничный пример асинхронной операции, для выполнения которой не требуется никакого потока, о чем и была речь в статье.

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

DMA — это не выполнение вычислений описанных у вас в async функции.
Когда у вас вычисления внутри вызываемой через await функции — они выполняется внутри какого-то потока.

и выполняемое операционкой действия глубоко внутри — тоже формально прикреплены к какому-либо потоку. то не имеет отнощения к мифу о внепоточности кода внутри await
Когда у вас вычисления внутри вызываемой через await функции — они выполняется внутри какого-то потока.

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


и выполняемое операционкой действия глубоко внутри — тоже формально прикреплены к какому-либо потоку

Потоки в ОС появляются на определенномуровне абстракции. На уровнях ниже есть драйверы, прерывания, DPC, которые работают "просто на процессоре" и никаких потоков там нет.

Чукча не читатель, видимо, ведь в статье все подробно было разжевано.

Что такое "код внутри await"? Можете пояснить на примере?

Во-вторых, «волшебным образом» он это делает потому, что это по сути и есть callback, о чем вам сто раз сказали. Но вы, видимо, знаете, как работает await, лучше, чем официальная документация и целая куча специалистов и экспертов.

вот как раз в документации то, ничего про callback не сказано.
Более того — там несется некая ахинея про код, выполняющийся вне основного потока, но без потока. Я согласен что возможно, async таск выполняется вне потока вашей программы, а в неком «сервисном потоке», который предоставляется .net-средой -исполнения. Но если у вас есть вычисления внутри async функции, они будут выполняться внутри потока.
А в документации которую вы показали рассказывается о некой «магии», и ни слова о том, как это действительно проиходит. Потому вы или покажите офф. документацию где явно сказано, что там действительно последующая часть кода выносится в call-back-функцию, или признайте что вы «не совсем правы». Потому что повторюсь — в документации рассказывается о неких «contonuation» — а что это — нигде не написано.

Более — того. По большому счету не важно, что там внутри. Считаете что там создается асинхронный байткод — ок, я допускаю, я не против. Я против того, что бы на основании того, _во_что_ превращается await в машинном коде, классифиировать исходный код.

Await позволяет вам создавать код в синхронном стиле, который при компиляции создает асинхронный. Да, он добавляет в эту синхронность особые точки в которых поток может заняться другими задачами пока выполняется код вызванный через await. Да, такое особое свойство. Но код последователен, синхронен, событий и обработчиков в нем не объявлено. Значит это синхронный код.

А то что вы считаете что при синхронности должен «блокироваться поток» — это следствие того, что вы привязываете определение к конкретной реализации.

бритва оккама, говорите? если код построен по логике последовательного выполнения операций, синхронного, выглядит как синхронный, выполняется как синхронный. подчинятся логике синхронного — значит код синхронный.

Исходный код. но не байт код, который получаем в итоге компиляции.

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


Во-первых, принципиальное отличие await от семафора — отсутствие блокировки.

давайте ещё раз)))
то что await, приводит к тому, что перебор операций исходного кода в данной точке приостанавливается (но поток не блокируется) — это хорошее свойство но с точки зрения поведения, эта логики кода, к понятию «асинхронность» он имеет отношение косвенное, связанное только с тем как оно реализовано.

Повторюсь — как вы будете классифицировать этот код, если он будет собран компилятором, который разнесет ваш код на 3 Runnable (до await, сам await, после await ), сложит их в очередь, и поставит на выполнение для некого планировщика? плаировщик будет брать на выполнение следующий раннабл только тогда, когда выполнится предыдущий.
первый и последний runnable он выполнит в gui потоке, сам await — в сервисном/фоновом.

Поведение будет точно таким-же.
«GUI-Поток» не блокируется, операции выполняются строго в нужном порядке, ждут друг друга без блокировки gui потока.

Классификация исходного кода изменится или нет? можете ответить на этот вопрос?
Поведение будет точно таким-же.
«GUI-Поток» не блокируется, операции выполняются строго в нужном порядке, ждут друг друга без блокировки gui потока.

Классификация исходного кода изменится или нет? можете ответить на этот вопрос?

при этом отмечу, что никаких асинхронных call-back в этом случае нет. нет ни обработчиков, ничего. Просто конвеер задач на входе планировщика.

от такой реализации — исходного ли «синхронность» исходного кода?
вот как раз в документации то, ничего про callback не сказано.
А в документации которую вы показали рассказывается о некой «магии», и ни слова о том, как это действительно проиходит. Потому вы или покажите офф. документацию где явно сказано, что там действительно последующая часть кода выносится в call-back-функцию, или признайте что вы «не совсем правы». Потому что повторюсь — в документации рассказывается о неких «contonuation» — а что это — нигде не написано.

Запросто: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern
Under the covers, the await functionality installs a callback on the task by using a continuation. This callback resumes the asynchronous method at the point of suspension.


И еще: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/chaining-tasks-by-using-continuation-tasks


In asynchronous programming, it is very common for one asynchronous operation, on completion, to invoke a second operation and pass data to it. Traditionally, this has been done by using callback methods. In the Task Parallel Library, the same functionality is provided by continuation tasks. A continuation task (also known just as a continuation) is an asynchronous task that is invoked by another task, which is known as the antecedent, when the antecedent finishes.


Но код последователен, синхронен, событий и обработчиков в нем не объявлено. Значит это синхронный код.

Где вы вообще взяли термин "синхронный код" и что это значит? Какая разница, написано оно текстом через события и обработчики или последовательно, если результат одинаковый? Синхронность/асинхронность — свойство выполняемых операций, а не текста, их представляющего. Мне без разницы, как писать код: через await или на callback-ах, результат будет одинаковый. Разве что на await это читаемо, а на callbackах мозг можно сломать.


Классификация исходного кода изменится или нет? можете ответить на этот вопрос?

Опять же. Я не понимаю, где вы взяли термин "синхронный исходный код" в вашей интерпретации. Приведите, пожалуйста, авторитетный источник.
Но если говорить об этом в ваших терминах, то нет, если в результате поведение не отличается от исходного, то "синхонность" не изменится.

"Синхронизация" и "синхронность" — разные термины, вообще-то, а вы их путаете.


Уместнее использовать термины "блокирующий" и "неблокирующий", тогда путаницы не будет.


А синхронизация — это механизм обеспечения порядка выполнения инструкций, и ей все равно, какой это код.

Знаете, я тут ради интереса поискал по странице, и слово "синхронизация" потребляется исключительно вами, vintage и dplsoft. Сдается мне, что не я путаю эти термины потому что я все это время говорил про совершенно другой.

Вы сможете дать определение "синхронности" без использования слова "синхронизация"?

"Синхронно" — это исполняется прямо здесь и сейчас.


"Синхронизация" — не имеет отношение к асинхронности. Вроде как общепринято означает использование семафорв/мьютексов/спинлоков/watever чтобы потоки не покусали общие данные.

Мне, в принципе, нравится идея отсюда:
https://toster.ru/q/125725

Код называется «синхронным», если он использует только блокирующие операции операционной системы, такие как ввод-вывод, ожидание по таймеру, ожидание события, например, завершение потока.

Когда же код использует только неблокирующие операции, то такой код является «асинхронным».

Ну если же код использует одновременно и блокирующие, и неблокирующие операции, то такой код называется «говнокод».

И самое главное: языковая конструкция `await` не является вызовом к операционной системе, поэтому её появление в коде не позволяет судить о том, является ли код асинхронным или нет.
Я бы лучше поделил код на «последовательный» и «не последовательный».

Параллельно выполняемый код — это был бы частный случай «не последовательного» кода, другой пример не последовательного кода — это каллбэк. А синхронизация — это способ сделать не параллельный код последовательным.

К сожалению, у нас много книг написано про параллельное выполнение, но никакого внимания не уделяется «не последовательному». А между тем это гораздо более частый случай учложнения наших приложений.
Вернее, «синхронизация — это способ сделать параллельный код последовательным»

*в некоторых отдельных местах, но в целом код останется параллельным. (1000 потоков скачивают файлы и увеличивают счетчик на 1 — скачивают параллельно, счетчик увеличивают последовательно)

А если вы останавливаетесь на текущей инструкции, до тех пор пока не выполнится что то независимое от вас — это синхронный код.

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


завершение процесса — не стоит называть асинхронным событием.
Вот если бы ваш таск присылал вам в случайные моменты времени сообщения, и вы на них реагировали — это было бы асинхронным событием.

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

И по-вашему, если я начал долгую асинхронную операцию IO, потом долго что-то считал, и потом «жду» результата начатой ранее операции через await — это типа все синхронно?
это запуск потока с последующей синхронизацией потоков.
И да, это, по логике/стилю работы работы — синхронный код.

Приведите пример «асинхронного кода» в вашем понимании.

а-синхронный код, в классическом, верном, понимании — это суть, событийно ориентированный код, который построен на колл-беках и/или обработчиках событий

примеры асинхронного апи: WinApi функции работы с последовательными портами, в которые вы передаете ссылку на функцию, которую надо вызывать, когда на вход поступят байтики. Qt-шные сигнал-слоты. Промисес в js (потому что вы передаете им функцию/ссылку на функцию)

int main()
{   ...
    serial->setOnByteIncomeCallBack(&onByteInome);
    serial->writeBytes(....);
}

int onByteInome(byte bytes[])
{     ...
}
а-синхронный код, в классическом, верном, понимании — это суть, событийно ориентированный код, который построен на колл-беках и/или обработчиках событий

Ну и чем await не угодил? Это ровно то же самое. Визуально (то есть в исходнике) он выглядит как синхронный код с ожиданимями и всем таким, потому что так и задумано. Фактически же await обозначает, что все ниже него будет callback-ом. Довольно грубое описание, но концептуально это ровно так.

Кто выглядит как утка и крякает как утра — того и называют уткой. Даже если внутри у неё конечный автомат и колбэки.

Такой вопрос задам просто, зачем нужна асинхронность? Как вы понимаете её задачу?

Да не нужна она. Даже в JS, на хайпе которого она выехала, её уже закапывают.

Думаю, на этом можно спор и закончить.

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

Я боюсь, в этом случае кстати надо использовать не асинхронность, а многопоточность, ибо как имеет смысл в одном потоке опрашивать по 10-20 серверов + барьерная синхронизация, а не делать 1000 тасок. Ну чисто так… Я бы так сделал по крайней мере.

Это же частности. Я бы поставил 1000 тасок, а там планировщик сам разберется, как их раскидать по потокам, создать ли 10, 20, 50 потоков или вообще все дать в одном. Это зависит в том числе от железа. На 1-ядерном ПК нет смысла заводить 20 потоков.
И в конце-концов неважно, один поток будет делать 1000 запросов к сервисам или несколько, если время на инициализацию асинхронного запроса заведомо меньше, чем время ответа сервиса.

> Я боюсь, в этом случае кстати надо использовать не асинхронность, а многопоточность, ибо как имеет смысл в одном потоке опрашивать по 10-20 серверов + барьерная синхронизация,

Одно другому не мешает. В чём проблема создать 1000 задач, которые планировщик сам распихает по потокам, как посчитает нужным?

Ну ладно ладно, убедили, молчу :(

> Да не нужна она. Даже в JS, на хайпе которого она выехала, её уже закапывают.

Закапывают её только люди, которые эту асинхронность не понимают по причине недостатка образовательной базы. Например, не наигрались с потоками в C++, не пользовали функции-генераторы в C# и т.д.

Ни фига себе — выглядит как утка. Синхронный метод вернёт byte[], а асинхронный — Task<byte[]>.

getDataSync() и await getData вернут одно и то же.

Погодите. То есть вы считаете, что если вы пишете await Task<int> и результат выражения можно присвоить в переменную типа int, то вызов блокирующий?

> getDataSync() и await getData вернут одно и то же.

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

Внезапно


async Task<int> ReadBytesAndGetWrittenByteCountAsync()
{
    byte[] bytesRead = await serial.ReadBytes(); // читаем с устройства долго и асихронно
    return bytesRead.Length; // возвращаем количество записанных байт
}

Эквивалентен:


async Task<int> ReadBytesAndGetWrittenByteCountAsync()
{
    Task<byte[]> bytesReadTask = serial.ReadBytes(); // читаем с устройства долго и асихронно
        var bytesCountTask = bytesReadTask.ContinueWith(x => x.Result.Length);; // когда таска выполнится, будет вызван коллбек и мы посчитаем количество байт
    return bytesCountTask // возвращаем задачу, на которой пользователь будет ждать
}

Что весьма похоже на ваш пример с onByteInome

UFO just landed and posted this here
UFO just landed and posted this here
Я бы хотел разобраться в вашей терминологии:
1. Вот это асинхронный код? Если нет, то что здесь блокируется?
def getBonusesByPersonEmail(email: Email): Future[List[Bonus]] = {
  val person: Future[Person] = db.findPerson(email)

  val purchases: Future[List[Purchas]] =
    person.flatMap(p => storeRestServce.getPurchases(p.id))

  val bonuses: Future[Seq[Bonus]] =
    purchases.flatMap(ps => Future.sequence(
      ps.map(p => bonusesSoapService.getBonus(p.promoCode))
    ))

  val validBonuses: Future[Seq[Bonus]] = bonuses.map(_.filterNot(_.expired))
  return validBonuses
}


2. А этот? Если нет, то где здесь «блокировки»?
def getBonusesByPersonEmail(email: Email): Future[List[Bonus]] = for {
  person <- db.findPerson(email)
  purchases <- storeRestServce.getPurchases(person.id)
  bonuses <- Future.traverse(purchases)(p => bonusesSoapService.getBonus(p.promoCode))
} yield bonuses.filterNot(_.expired)


3. А вот этот? (Тот же вопрос про «блокировки»)
def getBonusesByPersonEmail(email: Email): Future[List[Bonus]] = async {
  val person = await{ db.findPerson(email) }
  val purchases = await{ storeRestServce.getPurchases(person.id) }
  val bonuses = await{
    Future.traverse(purchases)(p => bonusesSoapService.getBonus(p.promoCode))
  }
  bonuses.filterNot(_.expired)
}

К сожалению я не знаком с этим языком, так что ничего определённого сказать не могу.


Тут я подробней всё это расписывал: https://habrahabr.ru/post/307288/

С чем знакомы? Java? Haskell? Знаете что такое монада?
Просто первый и последний примеры должны читаться и без знания scala. Достаточно понимать, что — Future — аналог CompletableFuture из Java или Task из C#. Методы map и flatMap работают так, как предполагается у монады.
Второй можно пропустить (аналог do-notation из haskell).

Первый асинхронный, ибо колбэки вызываются чёрт знает когда.


Последний синхронный с явно обозначенными точками синхронизации.

Вы удивитесь, но написано абсолютно одно и то же.


Если на пальцах, то:


async Task<...> A()
{
   ...
   var x = await B();
   ...;
}

эквивалентно


Task<...> A()
{
   ...
   return B().ContinueWith(() =>
   {
   });
}

Значит я не правильно понял последний код и он тоже асинхронный. В принципе весь FP — об асинхронном исполнении.

Но именно так работает async/await в Scala/C# и JS — вешает весь код дальше в колбек. На Future, Task и Promise соответственно.
Не находите, что все ваши рассуждения в этой ветке теряют смысл?
Но именно так работает async/await в Scala/C# и JS — вешает весь код дальше в колбек. На Future, Task и Promise соответственно.
Не находите, что все ваши рассуждения в этой ветке теряют смысл?
Вы зачем-то смешиваете логику работы откомпилированного байт-кода (после оптимизации, преобразований и трансформации) и логику исходного кода. Они могут быть разные.

Исходно, как я помню, мы говорили именно о логике исходного кода, и о том, что именно «синхронный» код позволяет описывать многие вещи проще и быстрее, чем событийно-ориентированный, асинхронный подход, потроенный на обработчиках и call-back.

и так:

Исходный код с использвоанием await — синхронный.
Байт-код/машинный код может быть каким угодно. И это не отменяет синхронности исходного кода.

Или вот для аналогии ещё пример: если я написал синхронные обертки над асинхронными функциями с call-back — то та часть исходного кода, которая использует обертки — синхронная. Вне зависимости от того, что внутри обертки — асинхронные функции.

или вот ещё загадка:
У меня есть абстрактный класс с одной функцией waitForBytes() которая не должна отдавать управлениедо момента своего окончания. Далее, я реализовал интерфейс в 2-х потомках — в одном я использвал синхронной функции, а в другом — на синхронных функциях и на асинхронных callback.

Скажите — код который использует интерфейс — он синхронный или асинхронный? если думать о том, что лежит под функцией, а не над тем, как оно должно себя вести, то у вас возникает ситуация, что классификация участка исходного кода будет динамически меняться. Что есть бред.

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


Вы зачем-то смешиваете логику работы откомпилированного байт-кода (после оптимизации, преобразований и трансформации) и логику исходного кода. Они могут быть разные.

Нет, они одинаковые. Трансформации, компиляция и все прочее не может менять логику.

Нет, они одинаковые. Трансформации, компиляция и все прочее не может менять логику.

Вы о какой логике говорите? Логика бывает разного уровня.
Если о логике и поведении описанном в коде высокого уровня — то оно сохраняется.

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

Более того, логика работы исходного кода и логика работы машинного кода — они обязаны быть разными. Но обязаны реализовывать одинаковое поведение.

Например, на уровне кодов процессора нет никакого ООП и наследования.
О какой одинаковости логики вы вообще можете говорить, если логика работы вашего исходного кода построена на ООП-понятиях?

Сохраняется поведение, но даже не всегда детали логики исходного кода.

Посмотрите мой ответ https://habrahabr.ru/post/334964/#comment_10352244
и скажите — оптимизирущие компиляторы — они какую логику меняют? и что они вообще меняют?

ну на другой вопрос про разную реализацию await- тоже хотелось бы услышать ответ. спасибо )

Вы зачем-то смешиваете логику работы откомпилированного байт-кода (после оптимизации, преобразований и трансформации) и логику исходного кода. Они могут быть разные.

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

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

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

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

Он легко может начать не умножать или суммировать, а дергать сдвиги, или, скажем, развернет циклы, если посчитает что так будет лучше. У вас в исходном коде — «визуально-однопоточный» циклический код, а на уровне машинного кода — 4 линейных параллельно исполняющихся цепочки.

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

А другой компилятор, может легко сделать не коллбеки, а 3 потомка и семафоры. Ведь согласитесь, await можно легко «сделать на семаформах».

Поведение программы, в обоих будет одинаковое, а логика и структура работы машинного кода (или байт кода) — разная.

И вот в такой ситуации — когда у нас реализация одного поведения, может быть построена на разных принципах, разной логике — скажите — как классифицировать сам исходный код? — по структуре его поведения, или по структуре того, что получается на выходе компилятора?

Вы, зачем-то, уцепились за выход компилятора. Вы уверены что это правильно?

Так это ВЫ уцепились за поведение компилятора. Я-то как раз говорю что с моей ТЗ это все асинхронно выполняющийся код, что там под капотом — стейт машины или гномики меня не волнует. Разной логики там быть не может, потому что одно является отображением другого.


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

Если в терминах маш. команд или байт кода цикл является джампом на лейбл начала цикла, логика цикла от этого не меняется


Поведение программы, в обоих будет одинаковое, а логика и структура работы машинного кода (или байт кода) — разная.

Я работаю не с байткодом, а с ЯП. Что под капотом — не важно. Это ведь ваше собственное утверждение :)

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

После «разворачивания циклов» у вас циклов как таковых не будет в байткоде, и не будет у вас ни джампов ни лейблов. Будет простой линейный код.

О какой «одинаковой логике» вы говорите? В сорсах у вас цикл и условия, а в байткоде — линейный поток вычислений без джампов и условий. это сохранение логики? О сохраннии какой именно логики вы говорите? не позорьтесь)))

или классифицируйте в терминах вашей вселенной, что именно делает с кодом оптимизирующий компилятор, и какую логику (или что именно?!) он меняет?

О какой «одинаковой логике» вы говорите? В сорсах у вас цикл и условия, а в байткоде — линейный поток вычислений без джампов и условий. это сохранение логики? О сохраннии какой именно логики вы говорите? не позорьтесь)))

Сохранения логики означает что используя средства языка я не смогу увидеть разницы между результатом работы скомпилированной программы от той, которая выполняется на процессоре, где этот язык является нативным. Это очевидное определение.

Сохранения логики означает что используя средства языка я не смогу увидеть разницы между результатом работы скомпилированной программы от той, которая выполняется на процессоре, где этот язык является нативным. Это очевидное определение.

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

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

так и компилятор. он сделает такие реобразования, о которых вы и не помыслите, но поведение останется таким же.

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

От жонглирования синонимами ничего не меняется. Если для меня нет способа найти разницу между двумя представлениями, значит этой разницы нет. Бритва Оккама во все поля.

От жонглирования синонимами ничего не меняется. Если для меня нет способа найти разницу между двумя представлениями, значит этой разницы нет. Бритва Оккама во все поля.

Получается, что «по вашей версии бритвы оккама» — оптимизирующий компилятор ничего не меняет? но по факту же он «что то» меняет — программа начинает работать быстрее, меньше ресурсов используется.

Так какую логику он меняет — можете описать? не уходите от ответа.

Конечно не меняет, по определению компилятора.


Compiler optimization is generally implemented using a sequence of optimizing transformations, algorithms which take a program and transform it to produce a semantically equivalent output program that uses fewer resources.

Если читали книгу дракона (ну или вообще что-то по теме), то "семантика" это и есть логика программы. И по определению он её менять не имеет права.

Вот только второй и третий — синтаксический сахар над первым.
Второе — синтаксическая конструкция, преобразующаяся компилятором к первому, третье — макрос, преобразующийся к первому.
По сути здесь 3 записи абсолютно одного и того же. Какой смысл разделять их терминологически? Или вы считаете, что асинхронность — термин, описывающий способ записи действий в исходном коде?
Смесь предметной реализации и обобщающих метафор( п.1 Требующих раскрытия в общем. П.2 И отдельно с реализацией). Термин вообще не привязан к компьютеру.
  1. Да, код асинхронный.
  2. Я не очень понимаю синтаксиса Scala, но если person имеет тип Future[Person], то мы не можем явно обращаться к person.id, будет просто ошибка.
  3. Вариация кода 1, переписанная с использованием async/await.
Да все 3 примера делают абсолютно одно и то же (во втором считайте, что каждая стрелка — flatMap). Мне просто интересно как будет обосновываться не асинхронность первого примера или разный ответ для каких-либо 2 из них.

await может вообще не создавать и не блокировать ни одного потока в принципе. Нужно просто осознать истину: никакого потока нет.

Он блокирует поток исполнения задачи, который flow, а не thread.

Вот только синхронность/асинхронность определяется в терминах thread, а не flow.

Раз вы не любите ходить по ссылкам, то процитирую:


Asynchrony, in computer programming, refers to the occurrence of events independently of the main program flow and ways to deal with such events.

Ну перепутали, бывает.

Да, await блокирует flow исполнения задачи, ну и что? Он блокирует flow задачи, а не вызвавшего задачу кода (main program).


P.S. Вот мне интересно, почему люди считают Википедию истиной в последней инстанции?

main program — такая же задача, как и все остальные.


Мне больше интересно, почему некоторые считают свои представления истинными, вместо того, чтобы хотя бы подумать над терминами "синхронное", "асинхронное", "синхронизация". Или попробовать найти хоть одно различие между "task", "fiber" и "thread".

main program — такая же задача, как и все остальные.

Ну и, собственно, что? Процесс, запустивший другой процесс, продолжит работу, не дожидаясь завершения вызванного процесса.


Или попробовать найти хоть одно различие между "task", "fiber" и "thread".

Task вообще никакого отношения к fiber и thread не имеет. Это просто объект (языка программирования), позволяющий отслеживать выполнение события и задавать продолжение — код, который будет выполняться по завершении задачи.


Ну а между Thread и Fiber основное различие только в том, что в первом случае планировщиком является операционная система, а Fiber — пользовательский код.


Теперь давайте вашу версию.

Процесс, запустивший другой процесс, продолжит работу, не дожидаясь завершения вызванного процесса.

А может и подождать, через await thread.join.


Task вообще никакого отношения к fiber и thread не имеет. Это просто объект (языка программирования), позволяющий отслеживать выполнение события и задавать продолжение — код, который будет выполняться по завершении задачи.

Я вам по секрету скажу fiber и thread — тоже не более, чем объекты.

Ну очевидно, он блокирует flow, в котором подразумевается, что какие-то операции выполняются после завершения какой-то асинхронной операции. Только с каких пор операция от этого становится синхронной?

Асинхронность, которая не блокирует flow — это fire and forget, частный случай "обычной" асинхронности, где мы после выполнения задачи все-таки хотим что-то сделать: узнать об этом, лампочкой помигать, что данные отправились и все такое.

Прочитал всю эту ветку и к концу как раз для себя сформировал, то, чем, мне кажется, является асинхронность. А тут как раз вы со своим комментом(= Именно fire-and-forget, как по мне, и есть тру-асинхронность.


Т. е. допустим мы имеем 3 блока кода: A выполняется до await в винсервисе №1, B находится на стороне, допустим, какого то WCF-сервиса и await-ится, C — зависит от результата B и выполняется после него. Да, между вызовом B и выполнением C может выполнится какой код выше уровнем (плюс .net еще дает бонус с более оптимальным использованием потоков): как понимаю, именно это вы с INC_R называете асинхронностью.


Но если мы вынесем блок B в еще один винсервис №2 и добавим какой-нибудь брокер сообщений: блок A будет отправлять команду (событие) блоку B, который теперь стал обработчиком. Блок С также теперь станет обработчиком других команд-результатов от блока B ("лампочкой помигать"). В итоге ни один из блоков кода не блокирует именно flow, работа каждой отдельной программы начинается получением и завершается отправкой сообщения и тут точно нет никакой синхронности. Поправьте меня, если что.

Слушайте, это все вопрос терминов. Если вы говорите «тут у меня асинхронный код» человек может неправильно понять, что вы имеете ввиду. Я с тем же успехом могу назвать синглтоном класс с публичным конструктором, который я создаю только в одном месте. У вас можно возникнуть серьезные претензии по поводу введения в заблуждение мной относительно того, как работает этот код. А я такой «не, то что ты мне скинул про паттерн — это не тру-синглтон, я для себя сформировал, что вот то, что я написал — это синглтон». Вряд ли в таком случае можно говорить о нормальной командной разработке.

Так что давайте придерживаться общепринятых терминов. А в них асинхронность это ровно то, что я (и куча других людей) написала выше.

vintage, кажется выше приводил цитату, насчет общепринятой трактовки термина.


А то, что описали вы, это скорее паралелльное выполнение части работы, потому что в итоге клиент, вызвавший метод где-то на самом верхнем уровне, все равно будет ожидать выполнения всех запущенных паралелльно методов XXXAsync(). Возможно они успеют выполниться за соответствующие промежутки "между вызовом B и выполнением C" (из моего первого коммента), а возможно нет, тогда клиент будет висеть и ожидать самый долгий await. Чем не синхронное выполнение?

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

Общепринятое определение — это не то, которое написал товарищь vintage, а которое встречается в толковом словаре, документациях крупных корпораций вроде майкрсофт/гугл/амазаон/мозилла/адоб/… Я уже приводил 2 ссылки на майкрософт и мозиллу, у человека почему-то одно и то же определение от мозиллы это ок, а от майкрософта почему-то сразу «не то». Ну а определение, что асинхронная операция это исключительно fire and forget нет ни у тех, ни у других.

Кстати, в пользу того, что асинхронность это нечто другое говорит само наличие термина «fire and forget», если бы это называлось «асинхронностью», не было бы необходимости придумывать новое определение.
Вы что-то прицепились к тому, что где написано.

Специально для этого привел пример — ответьте, разве для клиента вызов метода, который некоторые этапы работы выполнит одновременно, но, в конце концов, самую тяжелую часть все равно будет дожидаться, не является синхронным по сравнению с выполнением своих быстрых непосредственных обязанностей, постановкой тяжелых задач, например, в очередь, результат которых после выполнения будет передан клиенту, и отдачей частичного результата.
Если метод возвращает результат — то он сихронный, если промиз в том или ином виде — асихнронный. Будет он реализован на событиях, на тасках, на прерываниях или на коллбеках полностью на совести разработчиков языка и/или библиотеки.
В общем, напишу один раз тут и для всех:

Смысла спорить с определениями нет никакого. Определение не может быть истинным или нет, оно может быть только удобным или нет. Определение которое дает майкрософт/мозилла/этц — удобное, годное. Я им пользуюсь. Определение, которое предлагаете вы — используете только вы, причем не информируя человека о том, что вы под словом «кот» имеете ввиду «рыбу с 13 сегментами позвоночника», а не то, что люди обычно подразумевают. В итоге, чтобы понять, что у вас подразумевается под «асихронностью», потребовалось около сотни комментариев — это многовато. Что в очередной раз подтверждает, что это не очень хорошее определение, и оно может людей путать. Точно так же как #define TRUE FALSE в начале файла исходников. Они все еще читаемые, но уровень понимания кода резко ухудшается.

Так что я просто советую придерживаться общепринятых терминов. И если придумываете свои — то лучше называйте их по-своему, например «винтажсинхронность», чтобы люди сразу понимали, о чем речь, и не путались.
Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции, которая заявлена как «асинронная», и говорите что взаимодействие стало асинхронным?

Вот серьёзно, я не очень понял вашу мысль. Можете пояснить?


Отличие в том, что в случае асинхронного вызова исполнение не приостанавливается, а полностью прекращается. Конструкция await вызывает .GetAwaiter().OnCompleted(...), передавая делегат, который нужно вызывать при выполнении задачи, после чего происходит выход из функции.


То есть асинхронное программирование — это просто хорошо завуалированное событийное программирование.


А претензии к контексту выполнения мне вообще не понятны. Можно элементарно настроить планировщик так, чтобы выполнение продолжилось в том же контексте.


Например похожие технические эффекты, (похожие на await) можно добиться в Java 'без всяких await' с использвоанием yeld() — он отдает ресурсы потока на другие задачи, не не разрушая контекст исполнения.

То, что в Java для этого используют yield, означает только то, что в языке нет конструкции await, и поэтому приходится использовать не предназначенные для этого механизмы. Собственно, в C# до появления await тоже так делали.

То, что в Java для этого используют yield, означает только то, что в языке нет конструкции await, и поэтому приходится использовать не предназначенные для этого механизмы. Собственно, в C# до появления await тоже так делали.

Вы не поняли, там речь шла о операции yield для потока. Это почти то же самое что Thread.Sleep(1) в C#. То есть жуткий костыль, которому вообще не место в стандартной библиотеке...

Вы не поняли, там речь шла о операции yield для потока. Это почти то же самое что Thread.Sleep(1) в C#. То есть жуткий костыль, которому вообще не место в стандартной библиотеке...

Ой...

Асинхронная функция отличается от синхронной тем, что она возвращает управление почти сразу же. И async/await — это именно что способ написания асинхронных функций.

Асинхронная функция отличается от синхронной тем, что она возвращает управление почти сразу же.

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

«асинхронный» именно вызов, а не сама функция. и оператор await — занимается именно тем, что позволяет изменить синхронность вызова у функции, у которой стоит директива асинхронного вызова по умолчанию.
т.е. он позволяет приостановить выполнение потока инструкций в текущей функции, пока не закончится выполнение вызываемой функции.
сужу по официальным докам майкрософта.

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

идея таких директивы/оператора действительно интересная; выглядит удобно, позволяют не писать обертки. в джаве это желается несколько более громоздко.

но согласитесь, определение у функции директивы (?) «async», а потом вызов её с оператором await — это полностью соответсвует определению синхронного вызова: «выполнение инструкций в точке вызова, не продолжается пока не закончено выполнение инструкций в вызванной функции».

а если это соответвует синхронному вызову, то как же мы можем говорить про код, использующий await, что он асинхронный? он построен по логике синхронных вызовов. а то что где то там внутри есть асинхронные процедуры — это уже третье дело. исходник с await — синхронный.

А ничего, что можно сначала вызвать асинхронную функцию, а только потом, после выполнения другой работы, сделать await? Например, так:


var receiveTask = client.ReceiveDataAsync(); // началось получение данных в фоне
CalculateSomething(); // долго считаем что-то, пока параллельно получаются данные
var data = await receiveTask; // досчитали все, что хотели, теперь ждем получения данных

Это все еще синхронно, ведь await все делает синхронным, верно?

Я вам больше скажу, в нормальных языках вы можете написать так:


var receiveTask = client.ReceiveData(); // началось получение данных в фоне
CalculateSomething(); // долго считаем что-то, пока параллельно получаются данные
var data = receiveTask; // досчитали все, что хотели, теперь ждем получения данных

await — лишь визуальный шум для синхронного кода.

Это что за "нормальный язык" такой? И как транслятор понимает где ждать, а где не ждать? Неужели смотрит на суффикс имени переменной? :-)

Транслятор и не знает. ReceiveData возвращает прокси, который при попытке доступа к содержимому останавливает текущую задачу в ожидании прихода данных. Данный код на NodeJS + node-fibers: https://github.com/nin-jin/node-jin#sod

Тогда вот в этой строчке комментарий не соответствует реальности:


var data = receiveTask; // досчитали все, что хотели, теперь ждем получения данных

Ага, ну я не стал добавлять дополнительную строку использования data по назначению.

UFO just landed and posted this here

А зачем тут оно? Достаточно прокси-подтипа.

UFO just landed and posted this here

То есть вы пишете var a = b, при этом у a не тот тип, что у b? И как он это выводит, интересно?

Да нет, какой тип вернули, такой и записали. receiveTask вернёт Proxy!Json, который является подтипом Json, но с дополнительным поведением по синхронизации.

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

UFO just landed and posted this here
UFO just landed and posted this here

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

Приведите пример этого нормального языка, пожалуйста.

Любой язык, поддерживающий stackfull coroutines: D, Go, JS (node-fibers) и другие.


что мне делать, если нужно проверить, завершилась ли операция, а не ждать результата

Используйте флаги.


если я хочу дождаться результата и никак не использовать его

А зачем вам дожидаться результата, если он вас не интересует? И наоборот, если это не "запрос", а "команда", то зачем возвращать прокси, вместо немедленной синхронизации?


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

А зачем понимать на высоком уровне детали внутренней реализации метода ReceiveData? Это его дело, делать ли запрос к серверу, или сразу взять данные из кеша. А у вас — простой интерфейс.

Любой язык, поддерживающий stackfull coroutines: D, Go, JS (node-fibers) и другие.

А если я не хочу использовать stackfull coroutines? Например, есть >10000 кооперативных задач, и каждой выделять стек — расточительство. Чем вам функции-генераторы не угодили?


Используйте флаги.

Так и до busy wait недалеко.

Откуда у вас столько задач? Впрочем, ну потратите вы мегабайт 50 на все их стеки — это капля в море.

> Откуда у вас столько задач?

А вы не слышали про серверы, которые держат по 10к соединений?

> Впрочем, ну потратите вы мегабайт 50 на все их стеки — это капля в море.

5 килобайт на стек — не маловато будет? Подумайте ещё о процессорном кэше: после переключения между фиберами будет новый стек, который, скорее всего, в кэше лежать уже не будет. А значит, увеличится нагрузка на не очень-то и быструю память.

А вы слышали про сервера с десятками гигабайт оперативки?


В стеке всё-равно зачастую одни ссылки на объекты в куче, так что 5 кило — это даже много. Кроме того, Go, например, умеет стеки ресайзить.


Переключение стека стека происходит относительно редко. А вот переключение между асинхронными функциями гораздо чаще и они тоже далеко не всегда удачно лежат в куче.

Используйте флаги.

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


А зачем вам дожидаться результата, если он вас не интересует?

Потому что иногда меня интересует, что операция выполнилась, но не интересует непосредственно результат. Например, асинхронная операция меняет что-то в БД и возвращает количество измененных записей. А в данном случае меня это количество не интересует. В другом месте оно нужно, а здесь — нет.


А зачем понимать на высоком уровне детали внутренней реализации метода ReceiveData?

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


public async Task<int> GetCurrentCounterAndIncrement()
{
  var counter = await client.GetCounterAsync();
  client.IncrementCounter();
  return counter;
}

А теперь напишу по-вашему:


public int GetCurrentCounterAndIncrement()
{
  var counter = client.GetCounter();
  client.IncrementCounter();
  return counter;
}

Ничего, что в этом случае неочевидно, что client.GetCounter() асинхронен и теоретически операции могут выполниться в таком порядке, что я сначала обновлю счетчик, а потом получу уже новое значение?

Да нет, по моему будет так:


public int GetCurrentCounterAndIncrement()
{
  var counter = client.GetCounter();
  client.SetCounter( counter + 1 )
  return counter;
}

Ничего, что в этом случае неочевидно

В соответствующем окружение вполне себе очевидно, что любая операция может:


  1. быть асинхронной.
  2. кинуть исключение.
  3. записать что-то в лог.
    ...
Да нет, по моему будет так:

Нет. Потому что если между этими двумя вызовами кто-то другой успел поинкрементить флаг еще 50 раз, вы потеряете информацию об этом. Это не тот же самый код.


В соответствующем окружение вполне себе очевидно, что любая операция может

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

Ну а в вашем случае, будет возвращено не актуальное значение счётчика. Если оно использовалось для генерации идентификаторов, но привет коллизии.


Пишите на ассемблере — будете точно всё знать.

Ну а в вашем случае, будет возвращено не актуальное значение счётчика.

А я сказал, что мне нужно актуальное, чтобы генерировать идентификаторы? Мне атомарность не нужна, если я хочу, скажем, показать пользователю число просмотров поста и одновременно увеличить его на 1. Если он увидит, что просмотров было 10, а не 17, ничего страшного не случится. А вот увеличение значения счетчика должно работать атомарно.


Пишите на ассемблере — будете точно всё знать.

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

Вот и возвращайте ему актуальное значение счётчика:


public int GetCurrentCounterAndIncrement()
{
  return client.IncrementCounterAndGetIt();
}

Никакой язык высокого уровня не даст вам понимания что конкретно фактически происходит. По определению. Вы всегда работаете с той или иной абстракцией.

Вот и возвращайте ему актуальное значение счётчика

А если у меня нет такого метода? Если у меня есть только два метода: "получить текущее" и "увеличить на 1"?


Слушайте, я не буду изобретать наглядный логичный согласованный пример, когда это может понадобиться, чтобы покрыть все ваши возможные "а что, если". Вот голые требования. Операция Б должна быть выполнена строго после того, как окончательно и успешно выполнилась Операция А.


Если Операция А не дает мне понять, когда она там завершилась и завершилась ли вообще (ведь, по-вашему, мне не должно быть важно, как там внутри работает и когда), то что делать?


Никакой язык высокого уровня не даст вам понимания что конкретно фактически происходит. По определению. Вы всегда работаете с той или иной абстракцией.

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

Слушайте, вы бы хотя бы бугуртили по поводу того, что встроенные коллекции в C# по умолчанию — не thread-safe, а так — слишком слабая иллюстрация протекающей абстракции. В таких случаях обычно про IncrementCounterAndGetIt пишут в документации thread-safe ли он или же нет. Если вдруг по какой-то причине в документации этого не сказано, то я бы подумал кто писал этот метод. Если его писали не клинические дебилы, то внутри наверняка Interlocked

Погодите. Вот простой пример: мы что-то асихронно пишем на диск, и по окончании хотим залогировать, что запись произведена успешно. с await это будет


await drive.WriteBytes(bytes);
Logger.Log("Write operation suceed");

А что будет в вашем варианте?

!drive.WriteBytes(bytes);
Logger.Log("Write operation suceed");

Предельно очевидно. "Отрицание drive.WriteBytes()" Спасибо, заверните мне await.

То есть вся разница в том, какой терм будет обозначать ожидание — ! или await? И я правильно понимаю, что ваш вариант тоже асинхронный?

Оба варианта синхронные неблокирующие.

Я правильно понимаю, что:


  • синхронные значит, что 2 строчка выполнится после первой
  • неблокирующие значит, что этот же тред может заняться другой работой, пока ждем выполнения операции

?

У вас что-то с терминологией. Для операций ввода-вывода «синхронный» — это синоним для «блокирующий», а «асинхронный» — синоним для «неблокирующий».

В данном коде не только операции ввода-вывода.

Ещё запуск асинхронной задачи.

var data = receiveTask;


А если я просто хочу положить receiveTask в другую переменную — как мне быть в нормальных языках?

Извиняюсь, что поленился и ввёл в заблуждение. Правильно так:


var data = client.ReceiveData(); // началось получение данных в фоне
CalculateSomething(); // долго считаем что-то, пока параллельно получаются данные
var name = data["name"]; // досчитали все, что хотели, теперь ждем получения данных

Ну и тут у вас как раз блокировка, в отличие от асинхронного await, который ничего не блокирует.

Если вы про блокировку задачи, то await именно это и делает. Если про блокировку системной нити, то нет её тут.

Ну т.е. вы предлагаете компилятору (интерпретатор тут написать ИМХО сложно) анализировать все последующие использования data на предмет "а когда оно разворачивается"? Попробуйте сами на досуге написать такой компилятор. Да еще и так, чтобы он не ждал на доступе к data, а честно ставил все, использует данные из data в continuation (уж простите за дотнетовскую терминологию). А если напишете — попробуйте использовать такой язык и не запутаться что в какой момент исполняется.

Нет, я предлагаю компилятору не заниматься ерундой по созданию цепочек методов с потерей кол-стека. Волокна переключаются в рантайме по необходимости. Собственно я тут уже давал ссылку на свою реализацию этого дела на ноде: https://github.com/nin-jin/node-jin#sod

Ну и что у вас происходит в момент, когда чтение файла еще не завершилось, а пользователь уже требует контента?

А концептуально, чтобы не вчитываться в ваш код?

Запоминается ссылка на текущее волокно. Текущее волокно уступает исполнение другим волокнам. Обработчик события прихода данных берёт сохранённое волокно и возобновляет его работу. Таким образом внешнее API у ReceiveData получается полностью синхронным. А под капотом — асинхронщина и автоматический параллеллизм.

Да, волокна синхронны, здесь я с вами согласен. Отличие волокон от потоков — в пользовательском планировщике и явной передаче управления другому волокну.

А вот когда мы переходим к колбэкам и синтаксическому сахару в виде async/await, получается уже асинхронщина.

Нуу… сомнительная и неоптимальная схема. Из вашего описания очевидно, что волокно, которое вызвало ReceiveData может заблокироваться на неопределенный промежуток времени, если оно посмело обратиться к результату раньше времени и уступило место cpu-bound волокну, которое считает там… какой-нибудь 100500й знак числа пи. Прикиньте как это отлаживать на производительность. Попытка найти боттлнек сожжет все нервы даже бывалому ассемблероиду. Если это трейдофф чтобы не писать await — то я пас.

Вы не фантазируйте. await точно так же может заблокировать на неопределённое время. В любом случае профайлер покажет где чего ждала функция.

Да не блокирует ничего await, поймите уже, наконец! Он просто задаёт продолжение — колбэк, который вызовется по завершении ожидаемой задачи.

Ни разу. Коллбек от await-а вызывается сразу же после завершения асинхронной задачи, а не "после того как выполнится неопределенное количество других задач". (в общем случае конечно зависит от планировщика)

Ожидающее волокно тоже запускается сразу после завершения асинхронной задачи. Ребята, я устал вам объяснять простые абстракции. Высуньте голову из своего C# с костылями в виде транспиляции синхронного кода в конечный автомат. В нормальных языках есть более прямые и эффективные решения. Реально, почитайте про D, про Go.

Что такое "волокно"? Можете писать понятными и принятыми терминами? Та же корутина, etc

Я к тому, что я никогда не видел перевод термина fiber, волокно прям ухо режет. Лучше уже без перевода.

А вам ухо не режет, когда stream, thread и flow переводят одним словом "поток", а потом устраивают срачи из-за путаницы в терминах?


Нити и волокна — отличная метафора, показывающая как они друг с другом соотносятся.

Да в принципе, такая схема имеет право на жизнь. Просто CPU-bound волокон быть не должно. Все CPU-bound задачи должны в пул потоков отправляться, а запустившее такую задачу волокно йелдиться.

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

А потом появился await, и волокна я забыл как страшный сон.
если быть точнее асинхронный вызов запускает выполнение потока инструкций в параллели

Асинхронный вызов ничего не запускает. Конструкция await task вызывает task.GetAwaiter().OnCompleted(continuation), где continuation — колбэк — продолжение выполнения асинхронного метода, который вызовется при завершении задачи task.


Явно запустить в параллели что-то вы можете через Task.Run(...), но к async/await это уже не имеет прямого отношения.


«асинхронный» именно вызов, а не сама функция. и оператор await — занимается именно тем, что позволяет изменить синхронность вызова у функции, у которой стоит директива асинхронного вызова по умолчанию.

Изменение синхронности вызова функции? Бредятина какая-то. Начните с азов: почитайте, как устроена асинхронная функция, которая разворачивается компиляторов в целый класс, что такое await и раз раньше обходились без async/await, используя всякие там Task.ContinueWith.


но согласитесь, определение у функции директивы (?) «async», а потом вызов её с оператором await — это полностью соответсвует определению синхронного вызова: «выполнение инструкций в точке вызова, не продолжается пока не закончено выполнение инструкций в вызванной функции».

Разница в том, что асинхронная функция возвращает не результат, а Task.
Вы вызвали асинхронную функцию и, дойдя до await, функция просто вернула вам результат в виде Task, дальше вы можете дождаться завершения выполнения задачи с помощью await.

а если это соответвует синхронному вызову, то как же мы можем говорить про код, использующий await, что он асинхронный? он построен по логике синхронных вызовов. а то что где то там внутри есть асинхронные процедуры — это уже третье дело. исходник с await — синхронный.

Я считаю, что нельзя допускать человека к асинхронному программированию, пока он не поиграется годик-другой с обычным многопотоком, затем не помается месяцок с промисами и тасками без использования async/await. Только после его человеку можно разрешать писать await.


Асинхронный код действительно выглядит как синхронный by design. Но от этого синхронным он не становится.

Переписал (псевдокодовский микс C# и исходного кода):


async int initPrinter()
{
await serial->send( {0x??, 0x??, 0x??}); //запросим модель принтера
var originalTask = serial->waitForBytes(18);
var resp=await Task.WhenAny(originalTask , Task.Delay(5000)); //ждем ответ принтера - минимум 8 байт, 5 секунд.
if (resp != originalTask) return -100; // timeout
if (sizeof resp.Result <8 ) return -101; // ответ не распознан
String model = new String(resp.Result);
if ( ! model->startsFrom("Posiflex") ) return -102; // не поддерживаемая модель принтера
if ( ! model->equals("Posiflex PP5200") ) 
  {
     await serial->send( {0x??, 0x??, 0x??}); //запросим прошивку принтера
     resp= ...
     if (resp != originalTask) return -100; // timeout
     ...
     if (resp.Result[0]==0x23 and resp.Result[0]==0x24) // упс... у нас тут модель с прошивкой с известным багом
      {     
          await serial->send( {0x??, 0x??, 0x??}); //уст. кодировку 866 - в этой прошивке исходно 1251
          resp=...
          if (resp != originalTask) return -100; // timeout 
           ....
     }
  };
await serial->send( {0x??, 0x??, 0x??}); //переведем принтер в страничный режим с абс. позиционированием
resp=...
if (resp != originalTask) return -100; // timeout 
  ....
return 0;
}

Очевидно, что код полностью асинхронный. Говорить "а у тебя там await написан значит ты блокируешься!.. атата" значит просто плохо разбираться в теме. Если очень грубо, то компилятор переписывает так, что по завершении асинхронной операции выполнится коллбек, который представляет собой весь оставшийся ниже код.

Посмотрите на следующий код и больше так не делайте:


var originalTask = serial->waitForBytes(18);
var resp=await Task.WhenAny(originalTask , Task.Delay(5000)); //ждем ответ принтера - минимум 8 байт, 5 секунд.
if (resp != originalTask) return -100; // timeout
if (sizeof resp.Result <8 ) return -101; // ответ не распознан

А теперь обясняю, почему: в случае выхода из функции по таймауту задача originalTask продолжит своё выполнение. При повторном вызове функции будет либо ошибка, либо мусор.


Правильная реализация: с использованием CancellationToken, то есть:


            byte[] res;

            try
            {
                using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
                    res = await serial->waitForBytes(18, cts.Token);
            }
            catch (OperationCanceledException)
            {
                res = null;
            }

Ну и лучше это в функцию с try-catch обернуть для удобства.

Это в случае, если у таски есть параметр с токеном. Если его нет, то упс. А вот если есть, то так конечно лучше.


Мусора в первом варианте, как и ошибки, не будет, просто таска будет выполняться, я её результат никуда не уйдет. Не очень хорошо, но у нас нет способа оборвать таску если она явно не принимает CancellationToken.

В изначальном примере таймаут передавался прямо в вызываемую функцию, для клиента это в общем-то то же самое, что и токен. Можно было не накручивать снаружи Task.WhenAny

Ну кстати да, в рамках идеоматичности примера это больше подходит. Просто на моей практике почти все апишки не принимают "лишнего" параметра токена, из-за чего обработка таймаута происходит через такую обертку.

Просто на моей практике почти все апишки не принимают "лишнего" параметра токена, из-за чего обработка таймаута происходит через такую обертку.

Мне кажется, это связано с "нечестной" реализацией асинхронных функций: вместо того, чтобы переписать код под асинхронщину (если лень переписывать — есть AsyncRewriter), просто в лучше случае обернули синхронный вызов в какой-нибудь Task.Run, а в худшем — сделали синхронный вызов с возвратом Task.FromResult().


Поэтому отсутствие CancellationToken в таком API для меня — плохой признак.

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

Плохой пример. В HttpClient как раз таки используется CancellationToken.

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


async string GetFooAsync(int id)
{
   var url = GenerateUrl(id);
   using (var client = new HttpClient())
      return await client.GetAsync(url);
}
Потому что в клиенте-то он используется, а вот в коде, который его вызывает — нет.

Бить за такое по рукам надобно, если подобный вызвающий код представляет собой библиотеку.

Ну, я согласен, но разработка — командная работа, так что имеем что имеем :) На предыдущей работе я так и не смог объяснить, что в async-методе вызывать .Result не стоит. Так что по сравнению с этим определенно прогресс.

Почему же не стоит? Если Task.IsCompleted == true, то вполне можно.
Вот здесь я, например, целых два раза использую .Result.


Впрочем, мой код там таки содержит ошибку, и второй раз использование .Result недопустимо. Я забыл, что Task.WhenAny не бросает исключение, поэтому строчка должна выглядеть как:


return cur.IsCompleted ? cur.Result : throw new TaskCanceledException();

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


async string GetFooAsync(int id)
{
   var url = GenerateUrlAsync(id).Result;
   using (var client = new HttpClient())
      return await client.GetAsync(url);
}

Кажется, понял мысль. Асинхронное программирование имеет иллюзию простоты: типа, написал await, и всё хорошо.


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

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

Вы знаете, зависит от задачи, которую вы решаете. Мне довелось столкнуться с лютой многопоточностью в основном в настольных приложениях. Там без явного резервирования тредов было не обойтись. Я где-то выше уже говорил про задачу "приложение пытается подключиться к серверу, пока ты работаешь с локальной копией" — а как только подключилось — дожидается окончания последней операции, которую ты произвел, блокирует тебе доступ к локальной копии, покуда оно синхронизируется. При всем уважении, асинки здесь — только часть решения задачи. В общем случае — надо продумывать дизайн тредов и блокировки. То же самое касается ситуации, когда ты пишешь свой собственный сервер (например игроделы знают).


Так что, in conclusion, не все решается асинками, не все решается нодой, не все пишется на Javascript-е :)

Я придерживаюсь другого мнения: один раз примитивы написать можно и даже нужно. Велосипеды вообще писать полезно. Главное — не использовать их потом в продакшне.

Вы еще про .ConfigureAwait человеку расскажите чтобы совсем крышу снесло :)

Вы еще про .ConfigureAwait человеку расскажите чтобы совсем крышу снесло :)

Я его, кстати, считаю костылём. Не от хорошей жизни его создали.


В своих проектах я использую свой собственный планировщик, которому плевать на ConfigureAwait с высокой колокольни.

Да я говорил, пообщался с командой. Итог обсуждения "пока у нас нет проблем с производительностью асинков и дедлоками — писать не будем". Не скажу, что я доволен таким ответом, но посмотрим, что из этого выйдет.


Кстати полностью согласен, что это костыль, предпочел бы атрибут на уровне сборки (Fody в это умеет, к слову).

Самый простой способ избежать дедлоков — запретить вызывать Wait() или Result :-)


Это и правда самое простое решение, и выглядит куда аккуратнее чем постоянные ConfigureAwait...

Ну мы так и решили, потому что конфигурирование на "не нужен нам ваш контекст" выглядит как преждевременная оптимизация. Но тут возникает проблема с тем, что есть некоторые места, в которых можно написать только синхронно. А переписывание на асинхронное АПИ приводит к каскадным изменениям, кто конвертировал — тот знает. Но я согласен, что это нужно делать. Просто один коммит на 5000 файлов и который делается человекомесяц это весьма сурово, а постепенные изменения всегда приводят к тому, что где-то будет временная затычка в виде Result с потенциальным дедлоком.

Я пока пользуюсь советами из вот этого ответа, пока проблем нет. Я все же надеюсь за месяц-другой убрать все блокировки. Но спасибо за ссылку.

Попробуйте использовать AsyncRewriter для автоматизации этого процесса.

Если его нет, то упс

Если нет, тогда нужно написать соответствующий враппер, либо не использовать внешний таймаут вообще. Как вариант — скофигурить таймаут через свойство ReadTimeout.


Ну или можно, например, тупо закрывать файловый дескритор при таймауте, чтобы таск принудительно завершился. Костыль, но работает.


Мусора в первом варианте, как и ошибки, не будет, просто таска будет выполняться, я её результат никуда не уйдет.

А что будет происходить, когда одновременно будет несколько запросов на чтение?
Вот вы послали байтики оборудованию и ждёте ответ. Оборудование ответ прислало, но упс, результат оказался в предыдущем таске, который остался висеть с прошлого раза.

Если нет, тогда нужно написать соответствующий враппер, либо не использовать внешний таймаут вообще. Как вариант — скофигурить таймаут через свойство ReadTimeout.

Как вы будете писать враппер, не зная внутренней структуры? Если знаете, то проблем никаких, но мы сейчас про функцию которая по сути с драйвером идет. И если там нет таймаута, то его никак не прикрутить, если только не дописывать этот драйвер самостоятельно. С другой стороны, что в примере он был, но почему я именно так написал я уже объяснил — практика.


А что будет происходить, когда одновременно будет несколько запросов на чтение?

Зависит от устройства.


Вот вы послали байтики оборудованию и ждёте ответ. Оборудование ответ прислало, но упс, результат оказался в предыдущем таске, который остался висеть с прошлого раза.

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

Как вы будете писать враппер, не зная внутренней структуры?

Если ни один байт не должен быть потерян, то, например, так:


    interface IDeviceInterface
    {
        Task<byte> ReadByte();
    }

    class Device
    {
        readonly IDeviceInterface device;
        Task<byte> cur;

        public Device(IDeviceInterface device)
        {
            this.device = device;
        }

        public async Task<byte> ReadByte(CancellationToken token)
        {
            try
            {
                if (cur == null)
                    cur = device.ReadByte();

                if (cur.IsCompleted)
                    return cur.Result;

                var tcs = new TaskCompletionSource<bool>();

                using (token.Register(tcs.SetCanceled))
                    await Task.WhenAny(tcs.Task, cur);

                return cur.Result;
            }
            finally
            {
                if (cur.IsCompleted)
                    cur = null;
            }
        }
    }

Если же вместо одного байта читается несколько — без разницы, просто логика чуть сложнее будет.

Хороший пример, но ведь таска в таком случае точно так же остается висеть. От того, что мы заканселили таску мы прекратили её "типа", но при этом device.ReadByte() как читал, так и продолжает читать, и ничего про отмену не знает.

Хороший пример, но ведь таска в таком случае точно так же остается висеть. От того, что мы заканселили таску мы прекратили её "типа", но при этом device.ReadByte() как читал, так и продолжает читать, и ничего про отмену не знает.

Да, таска остаётся висеть, но так и задумано, потому что при следующем чтении мы просто подцепляем старую таску, а не запустим новую.


Ну а если же задачу хочется принудительно остановить с потерей состояния — пишите метод Dispose.

В таком случае ничего не мешало это сделать в примере выше:

Это действие (Task.Dispose) бесполезно, так как мы так не прервём выполнение задачи. К тому же, я имел в виду device.Dispose() — чтоб сразу гильотиной действовать. Есть шанс, что задача в этом случае завершится с ошибкой.


Моя же задумка в том, чтобы, наоборот, не останавливать задачу, но и не терять данные.

В плане "не потерять таску и подобрать её, когда она упала" задумка классная, надо запомнить.


К тому же, я имел в виду device.Dispose() — чтоб сразу гильотиной действовать. Есть шанс, что задача в этом случае завершится с ошибкой.

Но ведь device не реализует IDisposable.

Ну так реализуйте
    interface IDeviceInterface: IDisposable
    {
        Task<byte> ReadByte();
    }

    sealed class Device
        : IDisposable
    {
        readonly IDeviceInterface device;
        Task<byte> cur;

        public Device(IDeviceInterface device)
        {
            this.device = device;
        }

        public async Task<byte> ReadByte(CancellationToken token)
        {
            try
            {
                if (cur == null)
                    cur = device.ReadByte();

                if (cur.IsCompleted)
                    return cur.Result;

                var tcs = new TaskCompletionSource<bool>();

                using (token.Register(tcs.SetCanceled))
                    await Task.WhenAny(tcs.Task, cur);

                return cur.Result;
            }
            finally
            {
                if (cur.IsCompleted)
                    cur = null;
            }
        }

        public void Dispose()
        {
            device.Dispose();
        }
    }

Так речь шла о случае, когда он не реализует...

Ну если не реализует, тогда остановить операцию не получится — очевидно же.

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


Чтобы можно было продолжить работу с устройством после тайм-аута — надо где-то хранить текущее состояние — а в таком случае уже и асинхронные операции перестают выигрывать в понятности у событийной архитектуры.

Событийной архитектуре трудно быть понятной, потому что там флоу нелинейный. Приведите пример что ли, в каком случае EAP будет понятнее, чем async/await флоу?

Когда кто-то уже разработал конечный автомат для решения задачи.


Пример
             R:HDR @=======@ S:HDR             R:HDR[!=S:HDR]
          +--------| START |-----+    +--------------------------------+
          |        @=======@     |    |                                |
         \|/                    \|/   |                                |
     @==========@             @==========@ S:OPEN                      |
+----| HDR_RCVD |             | HDR_SENT |------+                      |
|    @==========@             @==========@      |      R:HDR[!=S:HDR]  |
|   S:HDR |                      | R:HDR        |    +-----------------+
|         +--------+      +------+              |    |                 |
|                 \|/    \|/                   \|/   |                 |
|                @==========@               +-----------+ S:CLOSE      |
|                | HDR_EXCH |               | OPEN_PIPE |----+         |
|                @==========@               +-----------+    |         |
|           R:OPEN |      | S:OPEN              | R:HDR      |         |
|         +--------+      +------+      +-------+            |         |
|        \|/                    \|/    \|/                  \|/        |
|   @===========@             @===========@ S:CLOSE       +---------+  |
|   | OPEN_RCVD |             | OPEN_SENT |-----+         | OC_PIPE |--+
|   @===========@             @===========@     |         +---------+  |
|  S:OPEN |                      | R:OPEN      \|/           | R:HDR   |
|         |       @========@     |          +------------+   |         |
|         +------>| OPENED |<----+          | CLOSE_PIPE |<--+         |
|                 @========@                +------------+             |
|           R:CLOSE |    | S:CLOSE              | R:OPEN               |
|         +---------+    +-------+              |                      |
|        \|/                    \|/             |                      |
|   @============@          @=============@     |                      |
|   | CLOSE_RCVD |          | CLOSE_SENT* |<----+                      |
|   @============@          @=============@                            |
| S:CLOSE |                      | R:CLOSE                             |
|         |         @=====@      |                                     |
|         +-------->| END |<-----+                                     |
|                   @=====@                                            |
|                     /|\                                              |
|    S:HDR[!=R:HDR]    |                R:HDR[!=S:HDR]                 |
+----------------------+-----------------------------------------------+

Учитывая, что async/await как раз-таки и разворачивается в КА, не вижу проблем. Как раз в этом случае можно просто воротить if else и всю эту логику, а с событиями придется попотеть. Каждый ведь с каждым нужно связать, и все эти взаимосвязи уследить в таком случае представляется не очень простой задачей.

Тот факт, что async/await разворачивается в КА никак не помогает писать код при заданном КА. Фактически, программисту нужно выполнить работу обратную той, которую делает компилятор.


А вот уследить за взаимосвязями — как раз нет проблем. Вот они все, на картинке.

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


В чем проблема по такой схеме написать что-то вроде:


var data = await port.SendDataAsync();
if (foo)
   await OpenPipe();
else 
   await OpenRcvd();

я, честно говоря, пока не понял.

Не вижу в вашем коде обработки ситуации, когда половина данных была успешно отправлена, а потом наступил тайм-аут...

Посмотрите внимательно:


  1. У вас в конечном автомате поток идёт сверху вниз без циклов. Вы никогда не возвращаетесь в предыдущее состояние. В этом случае плоский код с if-then-else может оказаться понятнее и проще в реализации.


  2. Управляющие команды всегда идут в строгом порядке — HDR, OPEN, CLOSE. Почему бы это не использовать в коде? Можно, например, уменьшить количество состояний, если полностью от них не отказаться.
Я бы так не сказал. Потому что после отмены чтения данных входящий поток оказывается в неопределенном состоянии, и единственный способ продолжить работу с устройством — начать все заново.

А это уже зависит от реализации. В моих задачах бывает, что устройство не ответило — ну случается такое, оно было занято, нужно просто его ещё раз попросить, и оно ответит. А если всё рвать и начинать заново, то это может занять пару минут.


Ну и ещё иногда бывает просто полезно остановиться не по таймауту, а потому что захотелось.


P.S. В любом случае, оставлять за собой висящие таски — это, по меньшей мере, некрасиво.

Очевидно, что код полностью асинхронный

Я, конечно, подозревал, что полное, безоглядочное погружение в идеологию и технологии мягкотелых вызывает… определенного рода… профессиональную деградацию…

Но то, что вы тут уже 5й пост устраиваете параолимпиаду по синхронным методам запуска асинхронных функции… и при этом заявляете что это «полностью асинхронный код»…

Я потрясен. «Такое» я вижу впервые.
Вы… в некотором роде, теперь, мой личный рекордсмен.
В отдельной, особой номинации. Для «тяжеловесов».

То, что вы тут язощеряетесь и демонстрируете «удобные и красивые методы» работы с асинхронными функциями, как раз доказывает, что далеко не везде асинхронность и событийно ориентированность — к месту. Потому что все что вы показываете — это как раз вариации на тему, как бы нам асинхронный вызов превратить в синхронный, и приостановть выполнение текущего потока инструкций (и поверьте всем глубоко плевать, блокируется при этом поток или освобождается, или начинается другой поток — это детали реализации. не важные по большому счету)
Да, эти методы позволяет не писать особых оберток, но это не делает ваш код «асинхронным».

То, что вы вызубрили костыли сишарпа — это конечно похвально, но даже то что вы показали, не поможет вам красиво и удобно, без лишних оберток работать с WinApi в котором вы должны «передать вниз» ссылку на callback функцию, которую надо вызвать, когда данные поступают.

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

Ну и заявление! Именно детали реализации — это и есть самое важное в асинхронном программировании. Если конкретно вам на это плевать — это ваши проблемы.


То, что вы вызубрили костыли сишарпа — это конечно похвально, но даже то что вы показали, не поможет вам красиво и удобно, без лишних оберток работать с WinApi в котором вы должны «передать вниз» ссылку на callback функцию, которую надо вызвать, когда данные поступают.

Видимо, вы даже не догадываетесь, насколько просто это стало делать с появлением асинхронных функций. Достаточно просто вызвать в колбэке TaskCompletionSource.SetResult(...), не беспокоясь о том, как этот результат будет потом использоваться.


И представьте себе, при использовании C# обёртки придётся писать и для обычных синхронных функций.

это и есть самое важное в асинхронном программировании

В конкурентном исполнении, а не асинхронном программировании.


Краткие определения для упорядочивания:


Параллелизм — одновременное исполнение разными исполнителями.
Конкурентность — попеременное исполнение одним исполнителем.
Асинхронность — исполнение без ожидания завершения подзадач.

В конкурентном исполнении, а не асинхронном программировании.

Да, верно. Ну и ещё один момент: асинхронное программирование является синтаксическим сахаром для событийно-ориентированного программирования. Иногда это бывает очень удобно.


Параллелизм / конкурентность — это относится к исполнению, это, фактически, свойство планировщика задач


А вот асинхронность / событийность — это относится уже к программированию.


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


Но нюанс в том, что существующие планировщики IO-bound операции выполняют конкурентно, а для запуска CPU-bound задачи нужно планировщик явно об этом попросить (Task.Run, ConfigureAwait и т.д.). И именно эта деталь реализации является принципиально важной.


P.S. Я, кстати, пробовал писать велосипед с Fibers — получался чисто синхронный код с конкуретным выполнением.

Конкурентность никак не привязана к одному. Одним, многими. А бывает что и некому — по факту :)
Потому что все что вы показываете — это как раз вариации на тему, как бы нам асинхронный вызов превратить в синхронный, и приостановть выполнение текущего потока инструкций (и поверьте всем глубоко плевать, блокируется при этом поток или освобождается, или начинается другой поток — это детали реализации. не важные по большому счету)

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

сначала, про поток в определении: вы цепляетесь за реализацию, причем даже конкретный частный случай сишарпа.

Раз это деталь реализации, значит бывают реализации, где и такое происходит.
не совсем верный вывод… могут, и есть, реализации, к которым само понятие «блокировка вызывающего потока» не применимо, а понятие «асинхронный вызов» — применимо.

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

вот скажем, о каких потоках и блокировках, мы можем говорить, если мы попытаемся определить, скажем что такое синхроные и асинхронные вебсервисы?

или вот: поток может быть вообще прекращен (как тут кто то говорил про то, как работает await), или под каждую инструкцию или группу инструкций будет свой поток создаваться (почему бы и не создать такую машину для байткода? проанализировать зависимости и распараллелить для оптимизации на многоядерном проце?) — вариантов масса, и понятие «блокировка потока» вообще будет неприемлемо — все зависит от реализации. особено, если мы говорим не про машинный код, а про машины исполняюшие байткод.

вы просили "пример асинхронного вызова, который блокирует вызывающий поток" — это конечно бред, но вот вам пример «асинхронного», для которого понятие вызывающий поток вообще не применимо: асинхронный вебсервис.
или вот еще «немного путарицы» пример: вызов синхронного вебсервиса может быть реализован на вызывающей стороне по синхронной локиге построения кода, так и по асинхронной.

Блокировка потока (который thread) «как признак асинхронности» — это частный случай.

теперь про определение асинхронности: вот немного скорректированное определние асинхронного вызова, которое я давал выше:

при асинхронном вызове запускается выполнение инструкций в параллели, а и не дожидаясь его кончания, управление возвращают в точку вызова (или сигнализирует о том, что выполнение инструкций запущено); в точке вызова, после этого, как правило, продолжается исполнение инструкций с места вызова.
при синхронном вызове, возврат управления в точку вызова не осуществляется (или сигнал об окончании исполнения не передается), пока не закончено выполнение инструкций в вызванной функции.


и совершенно не важно что у вас тут: сишарп, джава, или взаимодействие между двумя хостами при RPC или вызов вебсервиса описанного в wsdl.

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

в вашем примере ( в конце которогоо гордо заявляете «вот полностью а-синхронный код») — вы строите именно синхронный код, вызываете функции синхронно,… а потом заявляете что код «полностью асинхронный»… извините, но это… очень мягко говоря… не верно.

вы не переходите на следующие инструкции пока не выполните операции чтения из компорта. иначе вся логика работы кода разрушится.

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

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

я предлагаю прекратить говорить сугубо частными терминами сишарпа (таски, async/await ) когда рассуждаете об асинхронности. вы топик видели?))) вы понимате что такое опредение — это как минимум слишком узкое понимание? тем более, вы используете механизмы которые не столько относятся к асинхронному/синхронному вызову, сколько к потокам и синхронизации потоков.

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

В этом и заключается ваша ошибка. Вы никак не можете понять, что асинхронная функция вернёт результат (Task<>, Promise и т.д.) до того, как завершится её выполнение. Да, управление внутри функции не перейдёт на следующую после await строчку, ну и что с того? Вызвавший эту функцию код уже получит от функции результат.


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

Механизм async/await — это и есть синтаксический сахар для событийно-ориентированного программирования.

UFO just landed and posted this here
при асинхронном вызове запускается выполнение инструкций в параллели, а и не дожидаясь его кончания, управление возвращают в точку вызова (или сигнализирует о том, что выполнение инструкций запущено); в точке вызова, после этого, как правило, продолжается исполнение инструкций с места вызова.

Это ровно то, как работает await :)

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

Кстати да, я думал, что неплохо понимаю await, но этот пример неплохо иллюстрирует такое непонимание, в котором сам до недавних пор пребывал:


async Task Main()
{
    var sw = Stopwatch.StartNew();
    var a = Task.Delay(2000).ContinueWith(t => 0);
    var b = Task.Delay(2000).ContinueWith(t => 0);
    var aa = await a;
    var bb = await b;
    Console.WriteLine($"Result = {aa + bb}, Elapsed = {sw.Elapsed.TotalSeconds}"); // 2 секунды
}

Инлайним a и b:


async Task Main()
{
    var sw = Stopwatch.StartNew();
    var aa = await Task.Delay(2000).ContinueWith(t => 0);
    var bb = await Task.Delay(2000).ContinueWith(t => 0);
    Console.WriteLine($"Result = {aa + bb}, Elapsed = {sw.Elapsed.TotalSeconds}"); // 4 секунды
}

Я раньше думал, что await в том числе запускает асинхронную операцию, а создание фактически эквивалентно new Task(...), а на самом деле скорее Task.Run.

setTimeout( ()=> console.log( 'one' ) , 10 )
console.log( 'two' )

Асинхронный блокирующий.

И в чем же тут заключается блокировка, можно узнать? Пока что я вижу только таймер и операцию, которые вообще никак не связаны, и в общем случае могут произойти в произвольном порядке.

Оба кода на время своего исполнения блокируют поток, но исполняются асинхронно.

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


puts "hello world"

не является "блокирующей поток", потому что под блокировкой потока обычно имеют ввиду "усыпим тред, пока нас кто-то не разбудит". Condvar/LockFree/всякие циклы ожидания, это все детали реализации, но суть одна, блокировка потока — это когда мы ждем сигнала на продолжение работы, а на это время поток усыпляем, что и называют его "блокировкой".


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

console.log — блокирующий io.

console.log — это библиотечная функция, что она под капотом делает меня не касается. Функция является синхронной, если она возвращает объект, и асихнронной, если возвращает промиз. Что там под капотом — не моя зона ответственности. Асинхронность задается на уровне контракта — интерфейса — а не на уровне имплементации.

Вы тред-то почитайте. Речь шла об асинхронном блокирующем коде. но вы правильно заметили, что (а)синхронность — это тип интерфейса, а под капотом там может быть всё, что угодно.

> Вы тред-то почитайте. Речь шла об асинхронном блокирующем коде. но вы правильно заметили, что (а)синхронность — это тип интерфейса, а под капотом там может быть всё, что угодно.

Неверно. Всё зависит именно от того, что находится под капотом, а не какой интерфейс предоставляет функция. Если результат синхронного (=блокирующего) вызова обернуть в Task, он сам вызов останется блокирующим, асинхронным он от этого не станет.

И наоборот, функции чтения типа stream.BeginRead, хотя и не возвращают Task<> и не позволяют использовать await, являются вполне себе асинхронными.
Если результат синхронного (=блокирующего) вызова обернуть в Task, он сам вызов останется блокирующим, асинхронным он от этого не станет.

В правильной реализации вообще-то станет.


v = new Promise(resolve=>resolve(1))
v.then(console.log)
console.log(2)

Выведет сначала 2, потом 1.

Ребята, извините что влезаю со своким капитанством. Я вижу что под (а)синхронностью у вас подразумеваются сразу две вещи: стиль написания кода и модель его выполнения.

Есть синхронный (блокирующий) код — его легко читать, т.к. все операции выполняются последовательно, но в некоторых случаях (самый частый пример — ввод-вывод) он работает медленно, т.к. каждая операция блокирует поток выполнения.
Есть асинхронный код (неблокирующий) — он отлично работает операциями типа ввод-вывод, т.к. не блокирует поток при их выполнении и позволяет в этот момент выполняться чему-то другому, но из-за этого его сложно читать, т.к. написанный код выполняется непоследовательно (пример: callback hell)

async/await и их аналоги позволяют совместить лучшее из двух миров: писать код, который внешне выглядит как синхронный (т.е. его легко понять, т.к. читается последовательно), но выполняется — как асинхронный (т.е. не блокирует поток выполнения на каждой операции).

Flies away.
> под блокировкой потока обычно имеют ввиду «усыпим тред, пока нас кто-то не разбудит».

Операционная система тоже может поток заблокировать, например, для выполнения операций ввода-вывода.

puts, console.log — это блокирующие операции. Как думаете, что будет, если вы перенаправите вывод, например, на дискету или в ком-порт с медленной скоростью?

Вызов console.log ничего не блокирует. Если блокируется внутри console.log — это, как я уже выше написал, не моя зона ответственности, и для меня этот метод полностью выглядит и ведет себя как обычный синхронный. Сколько там оно будет выполняться — beside the point, в определении синхронной операции нигде не сказано, какие временные рамки должны быть, чтобы она считалась таковой. Функция не может меняться от "синхронной" до "асинхронно" от того, посмотрели ли мы её реализацию или нет. Либо она синхронная — и тогда мы её просто вызываем и получаем результат, либо она асинхронная, и мы должны каким-либо образом подписаться на получение результата в будущем.

> Вызов console.log ничего не блокирует. Если блокируется внутри console.log — это, как я уже выше написал, не моя зона ответственности, и для меня этот метод полностью выглядит и ведет себя как обычный синхронный.

Как это не ваша? Ещё как ваша. Если вы, например, загружаете веб-странцу, вам должно быть принципиально важно, как реализованы операции с сокетами. Просто об этом должно быть написано в документации.

Если после вызова функции она моментально возвращает управление, а в консоль данные пишутся в фоне, то она асинхронная (=неблокирующая). Ну а то, что не возвращается Task — считайте, что это fire-and-forget.

И наоборот, функция может возвращать Task, но вызывать внутри блокирующие операции и возвращать в самом конце Task.FromResult (программист просто поленился и поставил такую заглушку). От того, что она возвращает Task, асинхронной она не становится.
Как это не ваша? Ещё как ваша.

С чего это? Я вызываю функцию int foo = bar(). Я что-то блокирую? Или ответ зависит от того, как написана bar?


Если после вызова функции она моментально возвращает управление, а в консоль данные пишутся в фоне, то она асинхронная (=неблокирующая). Ну а то, что не возвращается Task — считайте, что это fire-and-forget.

И наоборот, функция может возвращать Task, но вызывать внутри блокирующие операции и возвращать в самом конце Task.FromResult (программист просто поленился и поставил такую заглушку). От того, что она возвращает Task, асинхронной она не становится.

Так мы не про функцию работаем, а про работу с ней. await на Task.FromResult является асинхронной операцией, а вызов fire-and-forget в большинстве случаев говнокодом, потому что это как раз и есть "запустим асинхронно, но вызывающему коду не скажем". Но да, её вызов будет синхронным, потому что мы явно вызвали синхронную функцию, а что у нее внутри мы не знаем.

> Или ответ зависит от того, как написана bar?

Да, зависит. Признак асинхронности — продолжение работы после возврата из функции.
Да, зависит. Признак асинхронности — продолжение работы после возврата из функции.

Ну а тут я уже не согласен. (А)синхронность функции закладывается в интерфейсе. И по интерфейсу всегда можно сказать, асинхронная функция или нет. В терминологии шарпа очень грубо говоря, если функция возвращает Task — её использование асинхронное, в противном случае — нет.

> В терминологии шарпа очень грубо говоря, если функция возвращает Task — её использование асинхронное, в противном случае — нет.

А что насчёт Stream.BeginRead или Socket.BeginReceive?

Я же сказал "грубо говоря" :)


Что касается конкретно их, то оборачиваем в таск и тогда определение снова работает. И да, конечно же они асинхронные.

Ну значит, всё верно. Если функция возвращает Task<>, то мы имеет право полагать, что функция действительно асинхронная, за исключением случаев, когда имеет место говнокод, например, функция объявлена как async, но внутри только синхронные операции или, о ужас, обращение к синхронному task.Result.

Это и называется "говнокод". Если академичнее, то реализация функции не соответствует контракту.

Я понима~ — я просто привел пример кода, который может лежать за якобы асинхронной функцией, ломая всю малину. Уберите минус :)

Асинхронная функция — это не та, которая написана "асинхронно" (то есть нам не важно, что у неё в теле написано), а взаимодействие с которой происходит по асинхронному флоу — формирование задачи и ожидание её завершения. Что там под капотом — не важно, Task.FromResult, реальный запрос по сети/к устройству или еще что-то.

Про AngularJS в 2017-том писать… Как буд-то статья была написана несколько лет назад.

Официальный релиз AngularJS 2 состоялся 15 сентября 2016 года.

AngularJS называют Angular 1. Тот что 2 называют просто Angular. Ну и вообще, Angular сейчас ловит хайпа куда меньше чем React. У Вас как бы наоборот.

А аналогичная градация для C#, Java и т.п. возможна? ;)

Скорее да, чем нет. Но я рискну предположить что так как JavaScript — в целом экосистема довольно новая и разработчики на ней ещё юны и весьма максималистичны (о чем свидетельствует их поведение) — то само существование подобной градации наиболее характерно для JS. И это эмпирически согласуется с моим опытом.

Ну JavaScript постарше C# будет…

Во времена, когда AJAX был неизвестным нововведением, а на горизонте маячил Web 2.0 — на C# не было лямбда-выражений и LINQ, но уже во всю писали мелкий банкинг и CRM-системы, а все холивары Java vs. C# уже отгремели канонадой. Так что формально JavaScript старше, но активно использовать и развивать его начали значительно позже.

О, кажется началось)

У меня 8 стадия похоже. Спасибо за статью. Пожалуй для меня уже поздно что-то менять :)


Раньше крайне подозрительно относился к ноде на бэкенде, а теперь просто подозрительно к этому отношусь. Считаю сабж слишком молодым, чтобы не обрастал детскими инъекция и и другими граблями, но потенциал есть.


Про многопоточность кстати всё не так плохо. Плохо конечно, но уже и прямой доступ к памяти есть, и кривоватая, но многопоточность на видяхах.


Уверен, что если нужна числодробилка с миллионом операций в секунду лучше использовать хорошие низкоуровневые языки, а всякий скрипт подойдёт для быстрого написания тормозного прототипа, который можно переписать на труёвый язык. Ты же уже полюбил переписывать код по несколько раз, %Юзернейм%?


Дорогой автор, мне нравится твой стиль изложения пиши ещё на тему веб-программистов в принципе. Мне кажется вебдевелоперы отдельная каста и часть советов для них не совсем подходят. Но это может быть я глупенький и не понимаю простых вещей в силу своего программисткого несовершеннолетия. Авось после 18 лет придёт озарение в мою голову, а пока учу JavaScript и совсем php забросил.

Пишу на C/C++. За плечами опыт ВЕБ-программиста (ещё не Frontend), когда jQuery был модным. Сбежал обратно на C/C++ от ВЕБ-а именно чтобы не иметь дела с людьми, которых описывает автор. Не считаю их идиотами или безумными фанатиками, но… Видимо мне с ними не по пути.

Конечно есть у меня и свои тараканы. Это, вообще-то, называется термином «профессиональная деформация». К примеру, привык считать программиста инженером, а инженер должен воспринимать программу не как проявление «машинного сознания» или, что ещё хуже, «магии», а как механизм. Т.е., программа — это механизм, продолжающий механизм самой электронно-вычислительной машины. Давно вы так «компьютер» называли? Очень давно? А, ведь, это — его суть.

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

А глубинная причина лишь одна: аппаратная абстракция. Категорическое нежелание понимать машину. Плохо это? Хорошо? Да пусть пишут на этом языке, если он им так нравится) Только пусть не ведут «войну с неверными». Это как воевать за всеобщую автоматизацию, надеясь на вечную выработку электроэнергии.
JavaScript — красивый язык. Он очень демократичный и лёгкий. Почти идеальный скриптовый язык, о чём и говорит его название.

Я же предпочту знать, как работает ЭВМ, и насколько нелепыми кажутся со столь «низкого» уровня все эти танцы уверовавших в абсолютную многопоточность JS-программеров. Да-да, я знаю, как работает механизм её обеспечения, а потому знаю, что многопоточности нет. По крайней мере в рамках одной ЭВМ. Есть иллюзия.

К слову сказать, сколь бы ни были противоречивы языки go и Rast, но всё же это — попытка поиска «золотой середины». Это всё вытекает из интересов бизнеса. Заметьте! Бизнеса, а не науки. Наука преследует порядок, а бизнесу он не нужен. Ему нужен заработок. Так что, терпите. Или изобретайте альтернативу. Есть же проекты вроде LLVM.
Вот тут очень хорошо написал товарищ. Есть тенденция не понимать машину, понимать задачу и решать её, а машина все сделает сама. Кому напрягаться, так это разработчикам второго круга, более «инженерным», те кто пишет интерпретатор для JS, а не на самом JS.
Исходя из одной лекции в техникуме по микропроцессорам, я в глубине души понимал, что нет никакой многопоточности вообще, есть хорошо организованное прерывание.

Многопоточность в рамках одной ЭВМ очень даже есть. Многоядерные процессоры, устройства на общей шине, DMA...

Так и полагал, что кто-то это напишет) Это не многопоточность, как ни странно. Ядра процессоров сидят на едином кэше третьего или второго уровня (у кого как), а доступ к этому кэшу — последователен для всех в порядке общей очереди. То же самое касается отдельных процессоров и ОЗУ. А то, что творится в рамках одного кристалла, это как исполнение макрокоманды. В общем, в этой ситуации многопоточность существует лишь на определённых уровнях приближения. Чтобы достичь настоящей многопоточности, нужно отказаться от архитектуры Фон-Неймана с её разделяемым доступом к памяти и устройствам.

А Вы, кстати, в курсе, что обращение к устройствам происходит через адресное пространство памяти? Если нет, то почитайте. Это — знатный костыль, который мы все воспринимаем как норму вещей) Последние массовые процессоры, лишённые данного костыля, были восьмибитными и имели отдельную адресную шину с сигналами переключения режимов доступа.

Да, общая память как "выжившая" массовая архитектура. Что поделаешь — она наиболее удобна для программирования. Но у каждого ядра есть собственный кеш и в рамках него они работают независимо (почти). Алгоритм синхронизации кешей L1 весьма сложен, как и модель многопоточности процессора, см. memory fences и x86 fence instructions.


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

DMA да. Это пример многопоточности. Вообще, сейчас у меня состоялся дискут с коллегой, не менее глубоко разбирающимся в архитектуре процессоров. Сошлись на том, многопоточность зависит от того, к чему она применяется. И всё зависит от того, как относиться к разделению общих ресурсов. Но даже так количество потоков не может быть больше суммарного количества логических ядер ЦП внутри ЭВМ.
Вот так и нужно было сразу написать, а не делать неверные даже в общем случае безапелляционные утверждения.
Для 4 и более процессорной- уже не актуально
Хорошо описано в «законе дырявых абстракций» — проблема старее и глубже
Мне понравилась статья, спасибо автору. Плюсанул бы, но кармы мало, так что только так. В первой статье текст был чересчур эмоционален потому что я находился под воздействием свежего погружения в React-Native проект от чего был нестройный слог плюс я забыл пару важных вещей ( в дополнение к тем что там упомянуты )

— кривая реализация integer в JS ( для многих задач крайне критично )
— в стрелочных функциях можно не писать return ( казалось бы ура ), но если там больше одной строки — без написания return спокойно возвращается undefined без всяких предупреждений
— неявные преобразования очень коварны — никогда на 100% нет уверенности какой же тип возвращает данный колбэк из DOM элемента / React компонента ( onChange, onSubmit etc ) и непонятно можно ли с ним безопасно проводить арифметические действия

В общем этот RN проект на ES6 был оставлен на совесть других разработчиков, никакого желания продолжать на этом писать нет. Похоже сработала естественная иммунная реакция организма на JS-мыслевирус, и болезнь не пошла дальше средних стадий. Так что теперь продолжаю писать на любимых языках и наслаждаюсь жизнью.
кривая реализация integer в JS

как может быть криво реализовано то, чего нету?)

без написания return спокойно возвращается undefined без всяких предупреждений

А почему функция без моего ведома должна что-то возвращать? Я понимаю, вы привыкли, что в функциональных языках так. Но JS — мультипарадигменный язык. Да, с однострочной лямбдой в 95% случаев мы хотим вернуть значение. С многострочной — уже не факт. А если принимающий код неправильно среагирует на это значение? Это не теория, а вполне конкретный случай факапа с coffeescript (в котором неявный return как раз).

неявные преобразования очень коварны — никогда на 100% нет уверенности какой же тип возвращает данный колбэк

Неявные преобразования не связаны в типом данных, который возвращает callback. И уверенности не будет в любом языке с динамической типизацией.

я находился под воздействием свежего погружения в React-Native

Т.е. я правильно понимаю, что вы взялись за коммерческий проект на незнакомой технологии в незнакомой экосистеме, облажались и написали разгромную статью про javascript?

Мне кажется, вы не представляете всю сложность «под капотом» технологии, которая нужна, чтобы запустить проект сложнее hello world на нескольких платформах.

React Native — это вообще не про голубое небо и пони какающие радугой. Это контракт с дьяволом: ты получаешь возможность быстрой кроссплатформенной разработки, а платишь своим опытом и готовностью решать проблемы.
Неявные преобразования не связаны в типом данных, который возвращает callback. И уверенности не будет в любом языке с динамической типизацией.

Вы путаете динамическую и сильную/слабую (strong/weak typing) типизацию. Первая про то, что переменная обладает типом только в рантайме, а вторая касается автоматического приведения типов. Например, python — dynamic, но strong: тип переменной определяется в рантайме, но, скажем число не приводится к строке при попытке написать "1" + 2, оно просто даст по рукам exception'ом.

Все верно, хотя умножение у последовательностей (в том числе строк) все же переопределено:
>>> '3' * 5
"33333"
UFO just landed and posted this here

Да, несколько странное поведение, но оно мне на enumerable/iterable вообще не очень нравится. Как и корявое a if b else c.

UFO just landed and posted this here

Выражение a if b else c возвращает a при истинности b, иначе возвращает c. В общем вывернутый наизнанку тернарный оператор.

UFO just landed and posted this here
Неа, не путаю. Разговор был про тип данных, которые вернет функция. В случае динамических языков он станет известен не раньше, чем функция отработает.
Похоже у Вас уровень 4 — «Вы просто не понимаете сути», только на Вашем языке. Какой там у Вас язык?
Написано же — JavaScript, ES6
А так — много языков на которых пишу. Основной который приносит деньги — Elixir / Erlang. Также люблю Scala, Clojure, Haskell
UFO just landed and posted this here

В любом туториале по стрелочным функциям (или туториале по особенностям ES6, где упоминаются стрелочные функции), в любом! — упоминается, что многострочные стрелочные функции требуют return.
Кто вам виноват, что вы не потрудились прочитать базовых вещей?

Диагноз
4 уровень: «Вы просто не понимаете сути»

То что есть один тип функций который меняет this, второй тип функций который не меняет this, при этом второй тип функций делится ещё на два — в третьем писать return со скобочками не нужно, а в четвёртом нужно, определённо говорит об удобстве, высокой продуманности и однозначности понятия «функция» в лучшем в мире мультипарадигменном языке и венце технической мысли, JavaScript.
UFO just landed and posted this here
Как удобно отослать к диагнозу вместо нормального ответа по теме.

… определённо говорит об удобстве, высокой продуманности...

Подобное можно написать о любой фиче в любом языке. А уж в функциональных-то — тем более.
А this в самом деле очень прост. Не говоря уже про return. Я даже описал всё это всего лишь двумя абзацами.

Немного негодования по теме
Если я приду в ваш язык и буду негодовать, что return где-то не требуется, что вы сделаете? Согласитесь «ну да, вот язык такой ужасный, сам мучаюсь» или пошлёте читать мануал?

Если я приду в C++ и заявлю, что деление на h и cpp-файлы некошерно, и вообще никто в 2к17 не подключает файлы к проекту с помощью директивы препроцессора, а все делают это функцией?
Если я приду в Python и заявлю, что язык ужасный, потому что в нём нет {}, а вместо этого отступы?
Да, в конце концов, если я приду в любой сильно типизированный язык и скажу, «сильная типизация — это фигня, и поэтому язык плохой»?

Нет, я так не делаю ). Потому что понимаю, что языки разные, разным людям по-разному удобно, и вообще (как мне кажется) тут имеет место аналог гипотезы лингвистической относительности.
И мне не совсем понятно, почему вы приходите в JS и заявляете о неправильности this (к слову, я могу назвать ряд действительно слабых (по моему мнению) вещей в js — например, typeof… но уж никак не полностью логичный this).


А с this на самом деле всё понятно и логично.
В JS функция и класс — это одно и то же. Только первая вызывается просто со скобочками, а вторая ещё и со словом new. Ну типа funcName() и new ClassName(). И это вполне естественно и очевидно, что в классе this указывает на экземпляр класса. В функции же this указывает на объект, если функция является методом объекта, в противном случае на глобал.

Стрелочных функций в js издавна не было — они появились только в ES6. Для простоты и удобства: теперь вместо длинного «function () {… }.bind(this)» можно написать просто "() => {}". При этом около многострочных стоит оговорка: в них нужен return.
UFO just landed and posted this here
Да, так вернее, спасибо.
Проблема с this не в том, что он сложен, а в том, что он нелогичен.

counter.increment() // сделает то что задумано
call_later(counter.increment) // не сделает вообще ничего и даже не выдаст warning/exception


Попробуйте вспомнить, как часто вы реально использовали великолепную фичу «потеря контекста в колбеке» и как часто вы с ней боролись, прибивая гвоздями контекст чтоб не отваливался (либо костыляли c замыканиями var self = this).

Никто в здравом уме не пишет методы которые работают в двух режимах: с контекстом и без него. Так зачем JS вставляет палки в колеса своим разработчикам? Когда каждому разработчику приходится бороться.с языком, проблема явно не в разработчиках.

Можно так:


call_later(()=>counter.increment()) 

Но я даже не представляю в каком языке ваша конструкция отработает верно.

Но я даже не представляю в каком языке ваша конструкция отработает верно.

Например, в C#.

Да я в курсе что можно либо создать замыкание (слегка уродливо и менее производительно), либо прибить гвоздями через bind (более уродливо). Именно это я и имел ввиду под стандартным поведением, с которым всем разработчикам приходится бороться.

А отработает корректно например в Python: у instance методов контекст не отваливается, у static/class методов контекста нет.
При этом, если очень хочется, оторвать контекст от метода можно, но мне за последние годы это понадобилось лишь один раз, для настолько магической фичи, что я до сих пор сомневаюсь правильно ли я сделал.

В Python методы объектов связаны с объектами, поэтому call_later(counter.increment) вполне будет работать. До несвязанного можно достать через counter.__class__.increment (правда, не всегда), вот этому придётся передавать counter в качестве self в любом случае.

Но я даже не представляю в каком языке ваша конструкция отработает верно.

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

В Python. Там функция является одновременно дескриптором свойства, что переопределяет поведение при чтении свойства. Для функций при обращении вида counter.increment возвращается специальный служебный объект под названием BoundFunction.


Ну и про C# уже тут писали. Там в рантайме делегат хранит не только указатель на функцию, но и, опционально, нулевой аргумент. И синтаксис языка позволяет в случаях вроде counter.increment эту возможность использовать.

И в java method reference вида counter::increment отработает так же в контексте объекта.

UFO just landed and posted this here

В java method reference пишется только через ::, других вариантов нет. Не знаю, что уж тут нечестного. В scala IIRC method reference будет выглядеть как counter.increment, если вам так захотелось прицепиться к синтаксису.

Скорее всего везде, где подобная запись вообще имеет смысл.
Эта запись чаще является частным случаем частичного применения.
Рассмотрим общий случай частичного применения (scala):
arg0.method(arg1, _, arg3, _)

Здесь на основе метода, принимающего 5 параметров (this + 4 явных), определяем лямбду, принимающую 2 параметра.

И даже если в языке нет полного синтаксиса частичного применения, работает та же логика: если указан параметр, то он применяется. Java:
Runnable r = counter::increment // без параметр (this применен)
Consumer с = Сounter::increment // 1 параметр


Проблема здесь в прототипном наследовании. В JS просто нет способа получить метод класса, не указывая конкретный экземпляр (так как нет классов в привычном понимании).
И в каком-то смысле поведение JS консистентно и логично — функция — это еще 1 поле объекта и, в данном случае, поведение такое же, как и со всеми другими полями. Но this в таком случае имеет смысл совершенно иной, нежели в большинстве других языков.
Проблема с this не в том, что он сложен, а в том, что он нелогичен.

Проблема с this не в том, что он нелогичен, а в том, что его логика отличается от других языков.

как часто вы реально использовали великолепную фичу «потеря контекста в колбеке»

Вы не понимаете. Это не фича «а давайте терять контекст, вдруг кому-то удобно будет». Это следствие его реализации. this — просто переменная, которая указывает, в каком контексте вызвана функция. То есть контекст не теряется, его никогда и не было, он просто подставляется в момент вызова. И такое следствие вы используете значительно чаще, чем вы думаете (все прототипное наследование на этом построено).

Вот смотрите.

var a = { 
  foo: function () { console.log(this) }
}

a.foo()


Тут вроде логично.

var a = { 
  foo: function () { console.log(this) }
}

var b = {
  foo: a.foo
}

b.foo()


Вот тут могут быть сторонники каждого решения.

var Proto = { 
  foo: function () { console.log(this) }
}

var B = function () {}

B.prototype = new Proto();

var b = new B();

b.foo(); 


А вот тут контекст должен указывать на Proto, по вашему, потому что там объявлена функция?

Другое дело, что в 95% случаев работа с this в JS крайне неудобна и новички об нее постоянно спотыкаются. Это да. Но она по-своему логична.

Define «логично».

Я считаю, что this логичен. This хранит контекст, в котором функция вызвана. Ну, TheShock уже всё отлично объяснил.

Смотрите, вот тут мне нужен родной контекст функции:
SomeClass.prototype.init = function () {
  this.element.addEventListener('click', this.onElementClick);
};

SomeClass.prototype.onElementClick = function () {
  this.style.color = 'red';
};
// так делать не очень хорошо, но не суть: легко придумывается более адекватный и правильный, но многословный пример


А здесь нужен контекст класса, и я просто делаю bind:
SomeClass.prototype.init = function () {
  this.element.addEventListener('click', this.onElementClick.bind(this));
};

SomeClass.prototype.onElementClick = function () {
  this.element.style.color = 'red';
};
Вы передаете в call_later один параметр — адрес метода, но не передаете адрес объекта контекста. Она угадать его должна?

Передаётся один параметр — метод. Не адрес метода, метод. Во многих языках (кстати, сейчас к ним присоединился даже дремучий VimL, в котором ни классов, ни прототипов нет, только словари) при таком обращении либо происходит связывание метода с объектом, либо возвращается уже связанный метод (а связывание происходит при создании). Это вполне логично, такое связывание позволяет реализации языка «не знать», как вызывается метод и что следующей операцией вообще будет вызов: просто происходит что‐то вроде «оператор точка достаёт атрибут объекта (достаётся уже связанный метод) и кладёт его на вершину стёка, затем оператор скобочки вызывает то, что находится на вершине». Или не вызывает, если такого оператора нет.

Чистой воды вкусовщина. Лично я не люблю, когда язык навязывает неявные действия. Любите, чтобы в одном параметре передавалось два объекта или некий новый костыльный метаобъект? Не вопрос, есть такие языки.

Вы ему: передай той функции метод этого объекта (функцию, которая умеет обрабатывать любой объект данного класса, у нас типа ООП),
Он вам: хорошо, но я неявно присобачу к нему и сам объект, чтобы ты не гневался, если вдруг имел в виду именно этот случай, а накладные расходы не были слишком низкие.
Вы о чем? this в JS как раз и является максимально неявным способом передачи дополнительного параметра в функцию. Вас почему-то не смущает неявный костыльный метаобъект, который передается при вызове counter.increment().

<expression1> == <expression2> // true
<expression1>() // делает мяу-мяу
<expression2>() // не делает ничего

В хороших языках, оператор «скобочки» — это просто вызов функции. А в JS — это анализ выражения перед скобочками, возможно подстановка контекста, и только потом уже вызов функции. Т.е. результат работы функции зависит не только от переданных параметров, а еще и то того, какое выражение было написано для того, чтоб эту функцию вызвать.

Вы ему: передай той функции метод этого объекта (функцию, которая умеет обрабатывать любой объект данного класса, у нас типа ООП),

Функция, которая умеет обрабатывать любой объект данного класса, это метод класса, а не объекта. В него объект должен передаться явно, через аргумент, а не через магический контекст.

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

Вы же не думаете что вся эта магия с контекстом на самом деле сделана для оптимизации накладных расходов? JS вообще-то практически всегда неявно присобачивает какой-то объект. И делает это при каждом вызове метода. То, что он не используется, не значит что его нет.
Функция, которая умеет обрабатывать любой объект данного класса, это метод класса, а не объекта. В него объект должен передаться явно, через аргумент, а не через магический контекст.

Увы, нету в JS классов в таком виде. Есть функция первого класса, которая создает объект и которому присваивает прототип. И даже нету методов. Есть функции, которые лежат в прототипе.

У нас даже нету «это объект класса А». Нет. Скорее так: «у этого объекта сейчас прототип ссылается на инстанс А». А может потом вдруг начать ссылаться на что-то другое

Не мыслите о this в JS с точки зрения C#, это не имеет никакого смысла
Классов нет, но абстракция «метод объекта» есть, именно поэтому контекст неявно и подставляется в counter.increment(). Проблема в том, что абстракция часто течет, например при работе с колбеками. Тот факт, что проблема объясняется устройством языка, не отменяет само существование проблемы.

Но веселье call_later(counter.increment) ведь не только только в контексте. Мало того, что язык легко позволяет допустить такую ошибку, так он еще и без проблем обратится к несуществующему свойству у window, извлечет undefined и увеличит его на 1. Никаких ошибок в процессе.

Я мыслю не с точки зрения C# (последний раз писал на нем более 10 лет назад), а с точки зрения того, каким должен быть промышленный язык. Особенно когда у него появляются ярые фанаты, которые считает что на нем можно (и нужно) писать абсолютно все.
Я говорю лишь о том, что это вполне логично с точки зрения внутреннего устройства языка.

О том, что это крайне неудобно — я совершенно не спорю. В том же C# лично мне этот момент нравится значительно больше — оно более интуитивно-понятно и значительно приятнее используется.
без проблем обратится к несуществующему свойству у window

Это давно уже починили в strict mode.

Она угадать его должна?

Но ведь угадывает же при вызове counter.increment().

В контексте вызова counter.increment() существует вычисляемое значение counter, потому угадывать ничего не нужно.

Когда вы вычисляете counter.increment и передаёте уже его значение в другую функцию, еще большой вопрос, должен ли кто-то услужливо тащить за вами оторванный counter или же делать точно то, что написано.

Выше справедливо отмечают, что модные языки так делают. Возможно, для удобства, чтобы не отвлекать девелопера в поездке до барбершопа на гироскутере. Если это такая востребованная фича, в принципе — нет проблем, простите брюзжание старого сишника.
Когда вы вычисляете counter.increment и передаёте уже его значение в другую функцию, еще большой вопрос, должен ли кто-то услужливо тащить за вами оторванный counter или же делать точно то, что написано.

Я считаю, что должен. А если я не хочу, чтобы контекст тащился вместе с методом, я просто создам статическую функцию increment с параметром counter и будут передавать "контекст" в виде counter в явном виде.

На самом деле она не указывает. Запись a.foo() обозначает "вызвать метод a.foo в контексте с a". Просто контекст по умолчанию не bound, как в Python.

Однако у автора чуткий инстинкт самосохранения. Ни единого слова про язык Go.

А что про go? Я не общаюсь с его фанатами. Пока что мне нечего про них сказать, за исключением того, что они много говорят про микросервисы, а когда им намекаешь что при микросервисной архитектуре неизбежна встреча с координатором распределенных транзакций — сразу начинают говорить что-то невнятное и уходить куда-то в тень.

Иногда eventual-consistency вполне достаточно, при необходимости можно сделать 2-phase commit.

Тут проблема в другом. Любители go обычно топят за микросервисы со страшной силой. Так вот меня интересует — если у вас один микросервис А обращается к микросервису Б, а тот в свою очередь — к миросервису С, то
а) как передать контекст транзакции между ними?
б) как откатить результаты работы микросервиса А, если микросервис С по какой-то причине выдал ошибку?


Здесь нужна некая третья сторона, которая, как ни странно, координирует распределенную транзакцию. Логично? Ну и вот ответа на этот вопрос мне пока ни один адепт go не дал. И я уже молчу о сложных кроссистемных случаях вроде транзакционная запись в файл + в базу (если ошибка — откатывается и то и другое).


В остальном — гугл неплохо пишет на go всякие низкоуровневые инструменты вроде небольших галактик серверов баз данных. И это определенно хорошо, но ИМХО не дает повода писать на go бизнес-логику и бекенд.

В контексте микросервисов это слишком общий вопрос. Тут нужно уточнять конкретный кейс.

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

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

Микросервис он как настоящий, только маленький. На Go я могу поднять такое на самом дешёвом VPS/VDS, на домашнем rapsberry за день работы или на C за месяц или на C# на физически выделенной железяке.

Использую python, могу поднять свой микросервис на домашнем rapsberry. Да, он занимает немного больше памяти, но только лишь немного)

координатором распределенных транзакций

так этого не только в Гоу нет — python, erlang например. Есть только в java и С#, что легко объяснить — в кондовых ООП-языках количество бойлерплейта для реализации такого рода машин состояния настолько огромно, что ни один мидл-программист его нормально не осилит. В go (как и в python) есть несколько популярных фреймворков для конфигурации и управления микросервисами, которые делают вашу задачу с распределёнными транзакциями не многим сложнее задачи управления локальными переменными внутри блока.


транзакционная запись в файл + в базу (если ошибка — откатывается и то и другое).

запасся попкорном, с удовольствием послушаю какие тут есть проблемы в Гоу и каким образом C# конкретно в данном аспекте поможет Гоу программисту


не дает повода писать на go бизнес-логику и бекенд.

Очевидно ваше утверждение противоречит практике. Существуют тысячи success stories распределенных отказоустойчивых, сложных систем на Go, в том числе в сегменте среднего бизнеса. Например телекоммуникационная платформы для операторов связи от ITooLabs (http://itoolabs.com). Есть тысячи опердней со сложной бизнес логикой на Гоу

Господи, мы все уже поняли что вы — горе-евангелист go. Хотя бы этот тред не засоряйте. Ну пожалуйста.

Ok. Предпочитаете кривляться и валять дурачка вместо того чтобы ответить за свои слова — слив засчитан, разговор окончен. А давайте вы тоже в качестве ответной любезности не будете больше писать откровенные глупости про "не дает повода писать на go бизнес-логику и бекенд", на которые не способны дать ни малейшего разумного обоснования, а?


PS: стесняюсь спросить, "мы все" — это кто, и почему вы считаете себя "их" фронтменом?

Вас вообще не смущает сколько людей минусуют ваши комментарии? Может уже пора задуматься?

то есть мне по вашему следует учитывать мнение тех, кто сказать ничего не может, но уверенно тапает в иконку, как мартышка, когда у него пятая точка подпекает из-за уязвлённой профнепригодности? чего ради?

"Я — красавец, go — няшка, а вы все — быдло", я правильно вас понял?
Вас мама в детстве не учила что так вести себя некрасиво?

UFO just landed and posted this here
Очевидно ваше утверждение противоречит практике. Существуют тысячи success stories распределенных отказоустойчивых, сложных систем на Go, в том числе в сегменте среднего бизнеса. Например телекоммуникационная платформы для операторов связи от ITooLabs (http://itoolabs.com). Есть тысячи опердней со сложной бизнес логикой на Гоу

Success story есть для любого языка, если вас это удивит. Вы ответили на комментарий про микросервисную архитекруту, но вы только сослались на какие-то фреймворки, которые вроде как работают, но они же не работают на магии. Если вы собираетесь синхронизировать состояние постоянно — это большие проблемы с производительности. Делаете кластер, например, etcd для этого — это еще нужно конфигурить и админить этот кластер и так далее.

В плане маркетинга у них все классно: хочется выучить язык хотя бы для того, чтобы с чистой совестью поставить на стол

вот такую штуку :)
image

А так крепкий хозяйственный язык. Микросервисный вопрос, правда, его немного испортил)

Статья в двух словах:


Ей, вы там, js разработчики, да, я не спец в этом вашем js, но слушайте меня, я вам сейчас поставлю диагноз!

И не лень же людям столько текста писать!

Комментарий в двух словах:

Ей, вы там, читатели комментариев, да, я не читал статью, либо не понял смысла, но здесь, похоже, затронуты мои чувства!
И не лень людям комментарии оставлять
Прекрасная статья, посмеялся, особенно видео про JS WAT :-)

После 9-го уровня возникает какое то безразличие ко всем этим джаваскриптам и на такие вопросы флегматично отвечаешь, что не видишь большой разницы между Java и JavaScript. К слову, на одном собеседовании в Амазон я также флегмантично ответил — интересно было реакцию посмотреть, а мне в ответ "значит вам всё равно на каком языке писать? прекрасно!"

Это отходняк и деменция, друг мой. Последствия быстрого слезания с JS.

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

Есть в этом доля правды. Но я же не могу написать что все программисты JS — плохие программисты. Во-первых в общем случае это не так, хотя среди них и много "нанятых по объявлению". Во-вторых тапками закидают. В-третьих это просто некрасиво.


А вот есть ли среди человеческих языков те, которые всерьез разъедают мозг переводчика — я бы с радостью послушал. :)

Я неудачно выразился. Не очень не в том смысле. А в смысле опыта. Хорошо владеющие языком и не очень. Т.е. когда ты в совершенстве владеешь языком, он не кажется тебе странным. Трудности возникают при переходе, особенно при смене парадигмы.
Когда я только начал учить английский язык мне не нравилось что там есть определенный и неопределенный артикль который, не до конца понятно зачем нужен. Потом я по воле судьбы выучил немецкий язык который тоже имеет определенный и неопределенный артикль. Но немецким, по прошествии шестнацати лет, я владею как вторым родным языком и мне артикль кажется уже не искуственной вещью (надо ставить, будем ставить), а естественной. И в английском языке, поскольку они родственные, артикль стал для меня такой же естественной вещью.

Наверно правильно не язык ругать, а то что его трудно учить. Наверно это будет более объективной критикой.

Есть аналоги Malbolge — ифкуиль, например

Вышеупомянутый корейский — ещё какой мозговирус. У меня, кажется, уже терминальная стадия КГМ: я захожу на Хабр почитать про языки программирования, а комментарий пишу, кхм, всё про то же.

Не могу согласиться. Естественные языки вполне себе можно сравнивать. И ругать китайский за упоротую письменность, а французский за проглатывание тонн букв в устной речи.

Это вряд ли будет объективно. Пока носители языка считают его удобным, количество критики в адрес этого языка будет просто выражением углового коэффициента кривой обучения для изучающих его.

Испанский и корейский это естественные языки. А вот эсперанто искусственный, его можно ругать.

… со временем превращает пишущего на нем человека в агрессивного фанатика, чего мне не удалось отметить с другими технологиями.

Лукавите.
В каждой шутке, есть доля шутки. У нас в компании C++/Java/Pascal разработчику никогда не поручат написать на JS что-то сложнее приветмира. А JS-программисты должны держать себя в рамках, чтобы не достигнуть четвертого уровня.
Здесь ты начал использовать npm

Почему вы считаете использование npm чем-то "больным"?


По вашему мнению, нормальные люди не пользуются менеджерами пакетов?

Это просто хронологическая метка, не более

UFO just landed and posted this here

Мне очень льстит что вы так беспокоитесь за мое финансовое положение, но все же уверяю вас — с ним все в порядке. Настолько в порядке, что мне иногда даже стыдно становится :)


Вообще апелляция к деньгам, конечно, самая хитрая. Скажешь что получаешь мало — де-факто согласишься с оппонентом. Скажешь что получаешь много — все подумают что хвастаешься. Не люблю такое. Поэтому намекаю что за свои знания строжайше типизированного языка, Страуструпа и магистратуру я получаю больше, чем квалифицированный JS-разработчик в Москве.

UFO just landed and posted this here

Я вам секрет открою — у меня еще время есть написать парочку opensource-фреймворков, один из которых на 60% состоит из кода на TypeScript и, как вы понимаете, это означает что JavaScript меня беспокоит, в отличие от Rust и Go (см. шапку статьи). И работаю я далеко не в столице, а прямо из дома.


Зачем писать статью? Ну например чтобы посмотреть как у вас припекает :)

UFO just landed and posted this here

Действительно можно засунуть что угодно. В каждый any ставится пользовательский тип данных, который пользователь может сам задать из C#-части. Как вы понимаете, этой информацией невозможно обладать на момент написания Lattice


Бтв, доку с примерчиками тоже посмотрите чтобы понимать что оно делает. Там не дописано 80%, но даст примерное понимание что я делаю.

Я правильно понимаю, что From может быть string, а to — DateTime? Если нет, то почему бы не сделать было IPrecomputedRange<T>, чтобы пользователь сам явно указывал необходимый тип? Потому как мне кажется, что IPrecomputedRange<string> != IPrecomputedRange<DateTime>, а у вас по сути они одного типа..

Эам… вы про какое место в коде говорите?

Вспомнил.
Слушайте, вы точно контекстно понимаете как используется эта структура, чтобы комментировать? Просто если я тут буду объяснять — будет пара экранов. Но в двух словах — да, any там — это ок, пользователь эту структуру не видит. Она сделана export из-за того, что в typescript нет internal.

Ну я посмотрел пару использований, и не нашел, почему информация о типе должна быть спрятана. Почему вот сюда например:


module Reinforced.Lattice.Filters.Range {
    export interface IRangeFilterUiConfig
    {
        ColumnName: string;
        FromPlaceholder: string;
        ToPlaceholder: string;
        InputDelay: number;
        FromValue: string;
        ToValue: string;
        ClientFiltering: boolean;
        ClientFilteringFunction: (object: any, entry:Reinforced.Lattice.IPrecomputedRange, query: IQuery)=>boolean;
        CompareOnlyDates: boolean;
        Hidden: boolean;
        DefaultTemplateId: string;
        InclusiveLeft: boolean;
        InclusiveRight: boolean;
    }
}

Нельзя сразу показать, что IPrecomputedRange тут от типа string? У нас есть FromValue — string, но ничто не мешает запихнуть любой object в качестве FromValue, причем ToValue может быть вообще третьим типом, несовместимый с этими двумя.


Я понял, что это "internal класс, у него полтора использования, и я всегда передаю только нужные и совместимые типы", возможно это придирка, но мне было интересно чем вызвано такое решение. Иногда типы явно передавать нельзя, согласен, но ведь это не тот случай? А если тот, то каков юзкейз который дженерик разрушит?

Нет, нельзя. Потому что конкретный тип range-фильтра задается пользователем на сервере и выводится из C#-класса. Получается что клиентский typescript-интерфейс (которого не существует после сборки) должен параметризовываться серверным C#-типом. В связи с чем я повторяю свой вопрос — вы точно понимаете как работает этот код?

Бтв, здесь FromValue/ToValue — сериализованное значение для начальной конфигурации range-фильтра. Циферки просто парсятся, дата — десериализуется из ISO, строки остаются как есть. Более того этот класс (Config) — автоматически сгенерирован из C#-кода. Вам точно хочется в этом разобраться или вы с какой целью интересуетесь?


К слову: в C# коде я тоже часто использую object, что не означает что я должен незамедлительно перевести этот код на generic-и.

Бтв, здесь FromValue/ToValue — сериализованное значение для начальной конфигурации range-фильтра. Циферки просто парсятся, дата — десериализуется из ISO, строки остаются как есть. Более того этот класс (Config) — автоматически сгенерирован из C#-кода. Вам точно хочется в этом разобраться или вы с какой целью интересуетесь?

Да, я пытаюсь разобраться, о чем я сразу и сказал.


К слову: в C# коде я тоже часто использую object, что не означает что я должен незамедлительно перевести этот код на generic-и.

Зависит от использования. Если это условный params — то не вопрос, а если условный object sender — то увольте.

Пишите в личку тогда — я вам отдельно расскажу. Там реально все сложно и информации очень много.

UFO just landed and posted this here

А в чем проблема? Успешный разработчик должен тратить всё своё время, что бы зарабатывать бабки и становиться ещё более успешным? :)

UFO just landed and posted this here
UFO just landed and posted this here
В статье упущены всякие PhoneGap, Electron и прочие. Вот уже где апофеоз бесполезности и фанатизма.
Скрытый текст
image

Ну кстати концептуально насчет бесполезности Electron я бы не согласился. Но только концептуально, то есть "ну наверное было бы неплохо писать десктопные приложения на html+js" (тем паче что многие так делают). Но в деталях реализации я ни черта не смыслю, поэтому не упоминаю.

Своя ниша есть, почему нет.
Пишутся же не плохие приложения на electron, тот же Sluck, Postman да еще и под все OS.

Сам отношусь к js-вакханалии примерно как описано в статье (основной язык — cs). Но тут надо делать одно небольшое веб-нечто, да еще требуют мобильное приложение. Я подумал, и решил использовать React + Cordova, чтобы сэкономить время, силы и прочее.


Доктор, на какой я стадии?

Ну… вы по крайней мере ещё умеете думать сами. Так что лично я вас благословляю.

UFO just landed and posted this here
Жду ответной статьи «Джаваскрипт как религия». С описанием почему именно эта религия единственно правильная и всем нужен радикальный Джаваскрипт. С пошаговым руководством как взрывать проекты написанные на технологиях неверных, чтобы потом переписать их на единственно верном технологическом стеке. Каждый кто перепишет проект на JS — получит в награду 72 сервера с NodeJS (после банкротства).

А если серьезно, такие фанаты появляются в основном у хайповых языков, с явными общеизвестными недостатками. Ну там типизация в Джаваскрипт или дженерики в Go. У хипстеров возникает чувство неполноценности, и они таким образом борются с ней. «От слабой типизации мы только выиграли». «ДЖЕНЕРИКИ НЕ НУЖНЫ!»

Казалось бы, прими недостаток и живи себе дальше спокойно. Идеальных технологий не бывает. Изучи другую технологию, сравни плюсы и минусы… Но нет, хипстерам нужен максимализм.
Да фигня в том, что часто происходит как раз наоборот. Приходит такой чувак из Java (спокойно, это только пример) к go-разрабам и говорит: go — говно, потому что в нем нет дженериков. А те сидят, хлопают глазами и думают, как объяснить чуваку, что у них и так все хорошо.
Вот об этом фанатизме я и говорю, отрицание вполне объективных проблем потому что «у нас все и так хорошо».

Роб Пайк вообще-то признает, что не все хорошо: «A weakness of Go is that any generic-type operations must be provided by the run-time. Some day that may change»

Жаль, что некоторые не хотят это понимать и бездумно защищают статус кво. Умение видеть собственные недостатки — бесценно.

Напомнило замечательную историю про Java, C# и лямбда-выражения. Когда их сделали в C#, джависты хором кричали — "нинужно!". А когда ввели и в Java — "как же мы раньше без этого жили-то божечки?"

Вы знаете, я писал за деньги на C/C++, C#/F#, Java, Swift, Python, PHP, Javascript/Coffeescript/Typescript. На гироскутере в барбершопы не езжу. И считаю стэк JS/node.js наилучшим что есть в настоящее время.

Знаете почему? Потому что в его экосистеме из п. А в п. В я быстрее всего доберусь. Единственное, что я не доверяю пока JS — это мобильные приложения (React Native очень сырой, ненативные гуи не люблю). Предпочитаю Xamarin (и нет повода для жалоб). Nuget — следующий за NPM репозиторий, позволяющий не писать, а конструировать приложения (получая за это деньги, а не ЧСВ).

Вобщем не язык решает. Хайпа хватает и в тусовке шарперов, и джавистов. Плюсисты вообще смотрят на всех как на детей :) А кто на ассемблере пишет, тот «в цирке не смеется».
Я в одном из прошлых тредов уже писал, что жизненный цикл проекта обычно несколько сложнее чем добраться из п. А в п. В.
Подход «максимально быстро из п. А в п. В» например приводит к максимально быстрому накоплению технического долга. Иногда стоит задуматься о том, что пункт В — не конечная остановка. У бизнеса конечеой остановки может вообще не быть.
Если вам нужно срубить 5 деревьев, можно взять тупой топор. А если деревья нужно рубить каждый день, возможно стоит инвестировать в бензопилу.

Хотя конечно бывают сферы где для разработчика это зачастую неважно (тот же фриланс).

Ну а с тем, что наколеночные прототипы на JS делаются быстрее, чем на каком-нибудь Java, вроде никто и не спорит.
Тот же Angluar веьма SOLID. И те же админки на том же Zend >= 2 я забрасывал не одну (и не с одним проектом на них уже) за то что они долгие.

p.s. во фрилансе никогда не работал.
Тот же Angular почему-то решил перейти на TypeScript.

Но проблема JS не в отсутствии фреймворков или библиотек (как раз наоборот, google: drinking game for web developers).

Проблема в том, что он не задумывался как промышленный язык, но в какой-то момент стал им. И в отличие от других пром. языков он не может развиваться, ломая обратную совместимость. Его посыпают сахаром и подпирают костылями, но 20-летние детские проблемы от этого не уходят. Это — объективно плохо. С этим можно жить, но этому глупо радоваться.
Я не вижу проблем с Typescript --> Javascript. Есть fullstack фреймворки — fireloop например — которые прекрасно работают только с Typescript.

Проблемы в том, что Javascript как язык не тот для меня нет. Я и начинал свое комментирование с того, что неважно на чем писать, важно как быстро экосистема тебе поможет достичь целей. У меня в работе сейчас один проект с первым ангуляром, не жалюсь, пишу… считаю что на PHP я то же самое писал бы значительно дольше.

Вся драма ява скрипта в том, что ВЫ ЕГО НЕ ВЫБИРАЛИ.


Вы выбирали веб, его выбрали преподаватели, статистика популярности, менеджеры, коллеги, экосистема, эйчары, ваша лень, его выбирали все кроме вас лично.


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


Его можно полюбить не смотря на его недостатки, что демонстрируют его поклонники. Но речь не про любовь.


И это беда всей нашей индустрии — обычно мы не выбираем лучшие технологии просто потому что они лучшие для нас. Другие делают этот выбор.


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


Но удел большинства все-таки идти за трендом. Просто потому что это тренд, который никто не выбирал.

Как же достали статьи из разряда «я пишу на правильном языке, а вы все мудаки». Остановите этот поток бесполезного самолюбования на Хабре.
Отложи JavaScript, освой какой-нибудь игровой движок. Тебе срочно необходимо отогнать от себя мысли о JavaScript

И тут такой скачиваешь Unity 3D и начинаеш писать на UnityScript.
Странная, ей Богу, статья. Да и профиль у вас чертовски странный:
делать всё, чтобы защитить бедных C# разработчиков от излишнего ныряния в JavaScript
Особенно в том, что касается «фанатизма» странности. В самой статье, да и в комментариях под ней вы упорно пишете, что вас раздражает не сама технология, но скорее ее через-чур ярые поклонники и высокомерие, с которым они смотрят на «не причастившихся».
Но, вот ведь парадокс, сама статья является воплощением подобного же высокомерия. Так как она буквально вопиет о важных умолчаниях: «те, кто выбирают .js добровольно — с вирусом в голове», ну и то, что ваш стек технологий и знаний по умолчанию дает вам моральное право смотреть на этих бедолаг с высока и рассуждать о том, что хорошо, а что плохо.
Но за юмором, за фразочками «я не имею ввиду технологию, а только фанатиков от нее» не скрыть негативных коннотаций. Все то же необоснованное высокомерие на пустом месте.
Не понимаю, честно, цели написания статьи. И так ведь понятно, что все «фанатики» — это в большинстве своем неофиты. Но ведь не только они выбирают этот язык в качестве инструмента. Есть и матерые зубры, которые с энтузиазмом относятся к нему.
Но умолчания… Серьезно, сначала вы называете какую-то особенность языка и потом говорите, что если человеку она нравится, то он «болен». Но ведь нет, вы же не имеете в виду всех, а лишь только «фанатиков». Мастерство риторики — запредельное. Сначала обгадить людей, хочу подчеркнуть, исключительно на основе того, что отношения к одному и тому же языку разные, а потом сделать вид, что этого как бы и не было.
И главное, чего добиться то хотели не понятно. Остроумием блеснуть за счет того, что потешаетесь над другими.
Очередной высокомерный повод для холивара на пустом месте.

Вы нашли в моем тексте какой-то странный, неведомый мне доселе подтекст. Если бы я действительно хотел полить JS фекалиями, а всех, кто на нем пишет назвать идиотами, то я сделал бы это в куда более жесткой форме, без юмора и аргументировано, с техническими выкладками. Но я признаю право тех, кто пишет на JS писать на чем им вздумается и как им вздумается, равно как и право любого человека любить все, что душе угодно — хоть тот самый '3' + 2.


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


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


все «фанатики» — это в большинстве своем неофиты

Доводы про строгую типизацию и многопоточность я услышал от senior js developer-а, который занимается программированием то ли 10 то ли 15 лет. Dixi.


Мастерство риторики — запредельное

Нет никакого мастерства риторики. Если вас кусает собака — то это жизнь и с этим надо жить. Если вам нравится когда вас кусает собака — вы со странностями. Если на основании этого факта вы убеждаете других, что им тоже должно нравится когда их кусает собака, а всех, кто с этим не согласен поливаете дерьмом — то вы больны. Именно об этом я и пишу. Обратите внимание на 8 пункт.

Обратите внимание на 8 пункт.

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

Ознакомьтесь с картинкой по ссылочке. Это вот как раз часть диалога с разработчиком из комментария выше.

Писать на JS не право а долг сегодняшнему web. Кто то должен это делать, а то упадут небеса. Выбора ведь нет. А без игрух на unity ещё пока можно обойтись. Пока C# экзистенциальный выбор, а не священный долг.

Обычная сублимация средней тяжести невроза, индуцированного латентной привязанностью к объекту отрицания (в детстве понравившуюся девочку за косичку дергали).
Высокий интеллектуальный уровень позволил автору отрефлексировать своё болезненное состояние, но не осознавая этого, артикулировать ему пришлось лишь в форме проекций в статье медицинской тематики.

Таким образом, уже очень скоро дорогой автор отринет C# и прочих языческих идолов, и вольется в стройные ряды адептов несравненного JS, который, как известно, любит и принимает абсолютно всех.

Интересная статья. Видно проблема назревает повсеместно, не только у нас в компании. С отделением фроненда и выносом его на ноду проблем особых не было. "Так модно, так современно". Появился независимый деплой фронта и бека. Теперь фронт может релизиться в два раза чаще. Нам понравилось) но JS-совцы решили пойти дальше и начали затягивать бизнес логику на фронт. Почему-то начали переписывать полностью готовые сервисы, которые никому не мешают на JS. У нас начался треш и угар. У кого-нибудь есть такое?

Я определил у себя 7 стадию, уже собирался действительно пойти работать грузчиком, но на полпути дошло, что пред-терминальную стадию у меня диагностировал сишарпист. No way, вы же проклятые микрософт-зомби =/

Мысленно убираем браузеры — и где видится javascript?

Вы меня убили (фигурально конечно)
Никогда не говори никогда (это я себе)
PS — а мне казалось (по собственному опыту), что для микроконтроллеров достаночно c или lua

А какая разница — JS или Lua?

Lua куда проще и прямолинейней. Простая стековая машинка, легко встраивается. Но зачем оно на контроллере со, скажем, 4k RAM мне не очень понятно.

Был не прав. Действительно регистровая, первый массовый язык не на стековой vm. Кому интересно — см. https://www.lua.org/doc/jucs05.pdf

Для игрушек — да. Для чего-то серьёзного отдавать 350k медленному интерпретатору, лишаться всяких прелестей типа DMA и т. п. Подобные вещи оставляют этот проект в нише ардуино, но на более мощном контроллере (хотя сейчас уже есть ардуины на cortex-m).


Вон в соседней теме автор радостно заявляет:


Вводит в заблуждение приставка Script и несерьёзный имидж языка, а на деле обнаруживается, что язык применяется от front-end и back-end до дескопных и мобильных приложений, программирования интегральных микросхем, обработки видео и в множестве других сфер.

Но пока видится только фронт, некоторый кусок бэка, некоторое количество десктопных приложений на электроне, react native и хватит.

А ещё есть уникумы, которые считают, что писать на js десктопные приложения и прошивки для микроконтроллеров — норма.

Пишу на JavasSript /* современном :) */, PHP, Python, C, C# – обожаю их всех. Хотя, в подобных статьях, в комментариях всегда интересно.

Немедленно достань учебник по C++

даже не знаю как отреагировать. То ли Наполеон ставит диагноз Гитлеру. То ли больному клаустрофобией прописывают ударные дозы суицидального чтива. В любом случае помощь квалифицированных медиков требуется обеим сторонам

Голосую за писательский талант автора )

C JS пятый год. Бывает позыв бросить, перейти на какой-нибудь C(#++). Но JS тут ни при чем.
Скорее тяготит безмерности спектра применений JS.

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

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

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

Вот и думаешь иногда, метнуться на какой-нибудь C(#++), может даже вспомнить Assembler, на котором писал когда-то драйверы. Сидеть себе копаться в однотипных задачах. Развлекать мозг алгоритмами, а не поиском npm-пакетов.

Но потом понимаешь, будет скучно без ощущения вечного ренессанса, без камасутры react/redux/apollo/immutable/draft/jest/eslint/node/npm/sass/webpack/babel/uglify/pm2. И не можешь никуда деться. Все симтомы болезни налицо )
Странно, что страйки многопоточности до сих пор летят в сторону JavaScript. В чисто виде, конечно же, таких механизмов в языке нет, но в браузере вы всегда сможете найти Web Workers, а в Node.js многопроцессорность с IPC и child_process. И это все, если очень хочется. Однако не надо забывать, что каждый «молоток» для своих «гвоздей».

«Компьютер — это конечный автомат. Потоковое программирование нужно тем, кто не умеет программировать конечные автоматы»

Алана Кокс, один из ведущих разработчиков ядра Linux


Видимо неточный перевод, программа это конечный автомат. Компьютер это машина Тьюринга способная воспроизвести любой конечный автомат, исполнить любую программу. О взаимодействии двух машин Тьюринга с пересекающимися алфавитом можно почитать в CommunicatingSequentialProcesses Tony Hoare. Такой вычислитель всегда теоретически возможно построить, но не всегда практически.

UFO just landed and posted this here

Это мета-стадия, находящаяся за пределами классификации. Люди явно хотят ASP.NET MVC, но почему-то повторяют его на TS

Автор, почему при таком неприятии overengineering JavaScript вы беретесь пилить свой 100500s vdom. Задача алгоритмически давно решена-diff() и patch() на дереве DOM, не представляет вызова для квалифицированного разработчика, прекрасно и неоднократно решена практически открытым кодом. Ценность подхода спорна. Почему не оставить предмет тем, для кого это хлеб насущный? Вы не инфицированы? Почему не порешать, недорешенную задачу распределенного консенсуса актуальную для микросервисов или block chain?

Ну во-первых это все же немного не ваше дело, чем мне заниматься и какие задачи решать. Вы меня, при всем уважении, на работу не нанимали и задачи не ставили. Во-вторых, это ни для кого кроме меня не есть хлеб насущный, ибо как шаблонизатор тесно интегрирован с C#, разбираться в котором для "настоящего шотландца JS-разработчика" недостойно внимания. В-третьих — вот дорешаю задачу с vdom-ом у себя, потом займусь распределенным консенсусом. Если захочу.


В-четвертых, я кажется сказал, что у меня нет претензий к JavaScript как к технологии. Нет? Я тихо сказал? Или у вас каждое 5 слово в тексте произвольно заменяется на "overengineering"?

Просто я тоже написал vdom. Это оказалось не сложно, но в конце я не понял зачем. Консенсус, византийские генералы это ни разу не просто, даже алгоритмы не все на Wikipedia лежат и у Кнута мало.

Может быть я глупый php-шник и совсем не в теме, но по моему нет ни одного языка который бы являлся серебряной пулей.


Видел я конторы которые пишут на c# методом копипаста не зная технологии как таковой (сам я тогда писал на visual basic в этой конторке).


Видел как мой тесть который всю жизнь делает системы на asm/c++ для кристаллов на сайте тещи который на php определил hex константы и определял mime загружаемых файлов читая в потоке несколько байтов файла и прочитанное побитово сравнивал с hex и определял mime по итоговой маске, хотя это решается встроенной функцией на раз-два.


В нашей конторе есть пара фронтов, что поддерживают проект на backbone.js и перепиливают его на vue.js, есть отдел из java маньяков которые пишут суровый javaEE копипасятя код из stack overflow и деплоят его на glassfish.


Есть и несколько php вроде меня которые пишут api пол 100500 сервисов и партнеров. Но вот бизнес и тим лид почему-то решили что внезапно лендос должен быть сделан на java+jsp+backbone/vue а вот CRM сделанную изначально на delphi и падающую 10 рад на дню нужно переписать внезапно на php.


И вот я блуждая по клубку процедур и функций в main.pas (благо паскаль в школе учил) вытаскиваю доменную логику и сую её в shedule комманды на laravel одним глазом наблюдая как чуваки с команды vue.js ругаются с java ситхами кто какую бизнес логику будет делать и на каком уровне за глаза называя друг друга нелестными словами.


В итоге я сделал вывод что все безумны и я в псих больнице я тупо дальше пилю скоринг на php, crm на php и еще кучу вещей на php которые не должны быть на php. В то время как эту чуваки уже 13 месяцев не могут ни допилить старый леднинг на backbone+javaEE ни переписать его на vue+javaEE.


Итог:


  • используете нужный язык в нужной ситуации и будет вам счастье
  • языки и стек технологии — это вопрос религии а мы должны быть атеистами

Ну вот почему так-то? Ведь все же знают и про KISS, и про каждой задаче свой инструмент, но всегда начинается дикая полемика (а иногда и с переходом на личности, что страшно), стоит кому-то просто высказать свое мнение…
Естественно, каждому ленивому (в хорошем смысле слова) программисту хочется иметь универсальный инструмент на все случаи, но такого не бывает. Дайте людям найти свои грабли и набить свои шишки.
Ну хотят люди писать бэкенд на JS, так пусть пишут (хотя я противник этого действа, см. каждой задаче свой инструмент), а вдруг, что толковое получится, ведь выбился же JS из роли "часики на страничке" на роль полноценного и самодостаточного языка для фроненда.
Кстати, и статью и комменты было интересно почитать.

Пора создавать пост «JavaScript как JavaScript».
а где шутки про вейпы, смузи и гироскутеры?
На днях смотрел исходники Radium-Keylogger думал что код так себе и надо писать такой же но круче на js. Это у меня какой уровень?
Как пациент, обнаруживающий у себя многие из описанных симптомов, могу предположить следующее: фанатизм джаваскриптеров может быть своеобразным защитным механизмом. Годами нас считали людьми второго сорта, а сам язык странной, тормозной, несерьезной игрушкой, которую солидные люди вообще отключают в своих браузерах. Но из-за его монополии все равно кто-то должен был на нем писать, а чтобы продолжать этим заниматься и не страдать от чувства собственной неполноценности, приходилось как-то компенсировать, поддерживать друг друга душеспасательными статьями, сопровождаемыми их картинками с логотипом JS на фоне карты мира, конференциями и т. д. Думаете, зачем кому-то понадобилось программировать на JS микроконтроллеры? Все для этого же. «Они думают, мой язык несерьезный, ну тогда я назло им буду использовать его везде, где только возможно, в самых неочевидных местах». И я не буду скрывать, я действительно ощущаю при мыслях об этом тепло во всем теле…

Можно сказать, что ради такого комментария я и писал статью.

А что тогда делать PHP разработчикам? Нас и по сей день считают людьми третьего сорта. Но мы же не лезем на фронтэнд и МК.

Не кидайте помидорами, но мне кажется, что сегодняшний JavaScript ближе к Java чем когда бы то ни было. Смотрите сами:


  1. Кроссплатформенность. Java на кофеварке сегодня запустить не так уж и прямо (профили, вот это всё), но браузер там скорее всего есть. И с помощью нескучного тюнинга бабеля, думаю можно под этот браузер писать со всеми радостями ES7+.
  2. Write Once Run Everywhere. Опять же за счёт бабеля (по желанию можно взять что-то другое) веб-приложение может быть собрано в какой-нибудь ES3 и запуститься на старых платформах.
  3. Язык/Платформа. Сам по себе язык довольно простой с несколькими неочевидными нюансами, которые спустя пару недель проклятий становятся уже не настолько неочевидными. Платформа, конечно, так себе — реализации отличаются, стандартная библиотека очень маленькая, но тем не менее.

М? Чем не каноничная Java? Осталось портировать Spring, чтобы было для чего конфиги иксэмэльные писать, и готово :)

Первые несколько лет жабаскрипт (es3) меня дико кошмарил. После «нормальных» ЯП типа PHP4, Clipper 5.2, Borland Pascal 7 он казался каким-то уродским уродом.

Тем не менее жизнь сказала «НАДО!» и никуда не денешься.

Потом во фронтенд бодро ворвался jQuery и жить стало неимоверно легче, кошмары закончились.

Потом были достаточно сложные проекты с кучей логики на фронтенде и выяснилось, что jQuery не решает.

Потом JavaScript внезапно начал нравиться, особенно после курсов зоракса и JavaScript Weird Parts. В голове наступило просветление.

А сейчас мне уже становится сложновато писать на PHP, потому что остро не хватает удобств JS…

В общем я безнадёжен.

P.S.: И да, куда ж мы без ноды и реакта… :)

"Не читал, но осуждаю". За вброс на вентилятор.
В статья описаны какие-то стереотипные видения и на их базе строится остальное. В-общем троллинг, фальсификация фактов и провокация. И ничего более.

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

534 other ways to reload the page with JavaScript
https://stackoverflow.com/questions/3715047/how-to-reload-a-page-using-javascript/43064234#43064234

И? Такие портянки можно составить на любом языке для чего угодно.

Там основных способов не так много — всего пять, большинство строк — просто игры с синтаксисом языка.

От перестановки self, location, reload местами, результат не меняется :D

Articles

Change theme settings