system silent channel что это
System silent channel что это
09.07.2014
автор: gidmaster
Лучшее решение для мебели – Quadro
Направляющие Quadro гарантируют безупречную работу ящиков на долгие годы. Для дизайнерской мебели, где важно спрятать технику от глаз, особенно подойдет скрытая направляющая, в которой все функциональные элементы спрятаны за фасадами и боковинами ящика. Там, где нужно максимальное качество и стабильность работы, Quadro – лучшее решение.
Направляющая Quadro создана для высокоточного и плавного хода выдвижного ящика. До 180 стальных шариков перемещаются по стальным направляющим каналам, день за днем обеспечивая прецизионную работу. Такая исключительная особенность, как продолжительная безупречная функциональность, стала возможной благодаря идеально отработанной технике Quadro. Давление и нагрузки на ящик с любой стороны поглощаются опять же благодаря направляющим Quadro, которые отвечают за стабильность по вертикали и горизонтали. Направляющие Quadro отличаются плавным и послушным ходом даже при полной загрузке ящика – принцип шарикоподшипника работает долгие годы.
Silent System – звук тишины.
Гладкие поверхности без ручек – Quadro с механизмом Push to open.
Минимализм в дизайне мебели сейчас на пике моды. И механизм Push to open для Quadro делает возможным использование гладких фасадов без ручек. С этим инновационным решением от Хеттих проектирование и производство фасадов без ручек становится простым и естественным. Механизм Push to open обеспечивает удобное открывание любых ящиков и выдвижных элементов легким нажатием на фасад. Легкое нажатие активизирует функцию автоматического открывания ящика. Даже если выдвигать ящик привычным образом, это не повредит надежным функциям Push to open. Push to open идеален для ящиков шириной до 900 мм.
скачать с Гугл диск
скачать с Яндекс диск Перечень основных преимуществ направляющих скрытого монтажа Quadro:
• Плавное скольжение направляющих благодаря технологии Quadro
• Мягкое и бесшумное закрывание благодаря Silent System
• Полное выдвижение – чтобы видеть и удобно доставать все содержимое ящика
• Можно комбинировать с PUSH to OPEN, механической системой открывания
• Долгий срок службы
• Удобная регулировка
Способы монтажа:
• Надвижной монтаж
Держа ящик горизонтально, просто поместите его на направляющие и задвиньте внутрь. Фиксатор автоматически надежно зафиксирует ящик на направляющих. Снять ящик с направляющих так же просто. Установленная высота фасада сохраняется при демонтаже ящика. Синее регулировочное колесико, встроенное в фиксатор, меняет положение клина между днищем ящика и направляющей, что обеспечивает идеальную регулировку фасада ящика по высоте без использования инструмента.
Что нужно знать о системе направляющих QUADRO:
• Silent System – демпфер, интегрированный в направляющую Quadro V6, закрывает ящики плавно и бесшумно.
• Идеальная стабильность в вертикальном и горизонтальном направлениях. Износостойкие направляющие каналы обеспечивают идеальное перемеще- ние стальных шариков внутри направляющей – для точной и контролируе- мой работы.
• Направляющие каналы не нуждаются в обслуживании благодаря функции самоочистки.
• Надвижной монтаж – просто поместить ящик на направляющие и задвинуть внутрь. Готово!
• Прочный фиксатор со встроенным регулировочным колесиком для настрой- ки высоты без инструмента.
• Взаимозаменяемость компонентов системы – замена Quadro частичного выдвижения на Quadro полного выдвижения без изменений размеров кор- пуса, ящика и передней панели.
Системы направляющих по типу выдвижения: полного и частичного выдвижения.
Схемы устройства и монтажа систем направляющих Quadro
— Ширина ящика равна:
Внутренняя ширина корпуса модуля для встраивания ящика минус 8 мм.
Соответственно если ширина корпуса модуля для встраивания ящика 500мм., то ширина ящика = 492мм.
Длина передней и задней стенки ящика = ширина корпуса модуля минус 40 мм.
Соответственно если ширина корпуса модуля 500мм., то длина передней и задней стенки ящика = 460мм.
— Высота ящика:
Необходимое пространство в корпусе определяется самостоятельно, но рекомендуется отступить от верхней кромки декоративного фасада ящика 10-20 мм.
Как повысить автономность телефона Android с помощью секретной настройки
Сегодня речь пойдет об «Адаптивных уведомлениях». Данное приложение получает доступ к уведомлениям пользователя, анализируя его поведение в приложениях, и на основе предпочтений сортирует уведомления в шторке, показывая наиболее важные из них в первую очередь.
Несомненно, оно может оказаться для вас полезным, если ежедневно вы получаете большое количество уведомлений, а также хотите, чтобы система сортировала и показывала вам первыми наиболее важные из них.
Но если для вас не имеет большого значения какое из уведомлений окажется в шторке выше остальных, то эту функцию можно отключить. Тем более что приложение постоянно работает в фоновом режиме и неминуемо расходует ресурс аккумулятора, а значит, после ее отключения можно ежедневно дополнительно экономить несколько процентов заряда.
Кроме того, в случае отключения, одним приложением, собирающим личную информацию, в вашем телефоне станет меньше. Об этом система открыто предупреждает вас после активации данной функции Android.
«Адаптивные уведомления» могут управлять уведомлениями из приложений и блокировать некоторые из них на свое усмотрение. Например, бывает, что вы ждете важное сообщение в одном из мессенджеров, а уведомление в шторке не появляется даже после того, как сообщение поступило, так как данная служба посчитала его менее приоритетным.
В итоге, вам приходится заходить в приложение и проверять сообщения вручную. Поэтому, если у вас есть проблемы с отображением уведомлений, отключение этой функции также может оказаться полезным.
Итак, какие преимущества мы получаем после отключения Android Adaptive Notifications:
Самый быстрый способ отключить службу «Адаптивные уведомления»
Перейдите в «Настройки» и в строке поиска введите слово «Уведомления», пролистайте немного вниз и выберите среди найденных функций «Адаптивные уведомления», а затем отключите службу Android Adaptive Notifications, установив переключатель на «Нет».
Три способа защиты разговоров по мобильному телефону от «прослушки»
Если Вы ищите простую в использовании, недорогую и мобильную защиту разговоров – эта статья поможет узнать о трех современных и легко доступных способах надежной защиты всей линии связи от прослушивания, а также о плюсах и минусах каждого из них. Чтобы не утомлять читателя, за рамками статьи остались сложные технические термины и понятия, а некоторые процессы описаны на абстрактном уровне.
Введение
Сперва пройдемся по «темной стороне» современной связи для лучшего понимания слабых и сильных сторон рассматриваемых решений защиты разговоров.
1. В голосовой связи есть «посредник»
В разговорах между абонентами всегда есть «посредник», который соединяет звонки или через который проходят разговоры – это программное обеспечение, помещенное в оборудование разного типа и вида (именуемое в простонародье «железо»). Пример посредников: офисная или городская АТС, сервера Skype/Whats App/Viber, базовые станции или голосовые шлюзы мобильных операторов.
Владельцы «посредников» имеют доступ к разговорам, сообщениям и всевозможной информации, проходящей через их оборудование.
2. Операторы связи анализируют передаваемую через них информацию
Звонки через мобильных операторов, звонки через интернет, посещение сайтов или чтение почты – это поток информации, которая проходит через проводные (беспроводные) каналы и оборудование операторов связи (посредников).
Домашний (офисный, гостиничный, ресторанный) роутер – это тоже посредник, имея доступ к которому, можно скрытно анализировать информацию, проходящую через него!
Работа анализаторов информации схожа с работой медицинского оборудования МРТ, которое заглядывает в любую область тела человека без хирургического вмешательства. Таким способом узнаются логины и пароли от почты «жертвы», набираемые им телефонные номера, адреса посещаемых сайтов, параметры мобильных устройств и компьютеров.
3. Троянские (шпионские) программы
За последнее время для смартфонов создано большое количество шпионских программ, которые скрытно записывают и скрытно передают злоумышленникам местоположение устройства, все что говорится в микрофон, все что передается через динамик или камеру, набираемый тест и многое другое, даже когда смартфон находятся в режиме ожидания.
Троян-шпион, попав на смартфон, может записывать разговоры до того, как они шифруются (запись микрофона и видео с камеры), а также после того, как они расшифрованы (запись динамика). Пожалуй, это один из самых эффективных способов обхода защиты разговоров.
4. Определение местоположения и идентификация личности
Использование услуг мобильных разговоров или мобильного доступа в интернет выдает местоположение пользователя и информацию об устройствах с которых осуществляются звонки и выход в интернет.
Зная местоположение мобильного устройства, можно также:
5. Операторы связи передают государству информацию своих абонентов
Почти в каждой стране мира операторы связи (посредники) вынуждены передавать государству информацию о своих клиентах по запросу спецслужб или силовых ведомств: какие сайты посещал, дата и время посещения, информация об устройствах с которого выходил в интернет, с каких IP-адресов получал информацию, с кем переписывается и созванивается, местоположение и т.д.
Мобильная и проводная связь – это тысячи километров проводов и тысячи единиц оборудования по всей стране. Это как большой лес с длинными корнями в виде коммуникаций, которые не переместить на другую территорию, если требования властей не устраивают. Поэтому операторы связи постоянно «сдают» своих клиентов государственным структурам.
6. Незаконно-законное прослушивание
Незаконным прослушиванием занимаются не только хакеры, специалисты с дорогим оборудованием, но и представители государственных структур. Почти во всех странах СНГ коррупция глубоко укоренилась в органы силовых структур, спецслужб и прокуратуры, сотрудники которых в личных интересах или «по заказу», используя служебное положение, организовывают прослушивание всех видов связи, которые «жертва» может использовать: мобильная связь, беспроводной и проводной доступ в интернет, городская телефонная связь.
Закон рассматривает прослушивание лишь как одну из разновидностей оперативно-розыскных мероприятий, для которых не требуется постановление суда. «Жертву» делают фигурантом уголовного дела, хотя бы в качестве свидетеля и его можно уже начинать законно прослушивать. И «жертва» об этом может никогда и не узнать.
Имея на руках подставное дело, отправляется запрос мобильным и проводным операторам связи, в котором требуется выдать всю имеющуюся информацию по их клиенту (история звонков, SMS, MMS, записи разговоров) или не препятствовать работе государственных технических специалистов, которые подключаются к линии «жертвы» для сбора информации.
В данном случает абоненту невозможно зафиксировать слежку, если она происходит на стороне оператора связи (посредника).
7. Подмена базовой станции
Государственные структуры и злоумышленники применяют для прослушивания мобильных разговоров специальное оборудование, вклинивающееся в беспроводную мобильную связь «жертвы». Для этого используются так называемые «мобильные подставные базовые станции», которые размещаются в непосредственной близости с «жертвой».
В последнее время этот метод набирает все большую популярность. Не далее 500 метров от «жертвы» размещается комплекс прослушивания размером с небольшой чемодан, который выдает себя за базовую станцию мобильного оператора. Все телефоны в радиусе действия подключаются к нему из-за более сильного сигнала.
Действия ложной базовой станции остаются незаметными для «жертвы», так как прослушанный сигнал перенаправляется на реальную станцию, при этом разговор проходит в обычном режиме.
Для смартфонов есть приложения, которые помогают определять наличие «прослушки» мобильной связи не операторами связи, а сторонними лицами: GSM Spy Finer, Android IMSI-Catcher Detector. К сожалению, использование таких программ для выявления подставных базовых станций не дает гарантии, что разговоры не прослушиваются на другой стороне собеседника.
С реалиями современной связи мы закончили. Переходим к обзору способов защиты разговоров на базе смартфонов. В статье к рассмотрению попали только те решения, которые:
Три способа защиты разговоров
Звонки через собственный сервер голосовой связи: BlackBox
Покупатели BlackBox получают собственный мощный инструмент защиты разговоров, который идеально подходит для использования узким кругом лиц, в компании или организации.
Принцип работы:
Основное преимущество использования BlackBox — защищенная голосовая связь без сторонних посредников, в которой покупатель самостоятельно распоряжается, кого подключать в «закрытый клуб», кого отключать и кому давать права на обслуживание своей системы разговоров.
Плюсы:
Минусы:
Звонки через платный сервис голосовой связи: Silent Phone
Сервис защиты разговоров компании Silent Circle набрал большую популярность во всем мире после скандала с массовым прослушиванием разговоров спецслужбами благодаря документам Эдварда Сноудена. Голосовая связь работает через сервера Silent Circle, к которым подключаются пользователи с помощью приложения Silent Phone. Используется такая же технология защиты разговоров как у BlackBox.
Принцип работы:
Защита разговоров всей линии связи (end-to-end) работает только с теми, кто установили себе Silent Phone и также прошел процедуру регистрации. После того как собеседники обменяются своими логинами, они могут звонить друг к другу.
iOS | Android |
iOS | Android |
На заметку!
Наивно полагать, что если пользователь запретит приложению использовать геоданные смартфона, это позволит скрыть его местоположение. Silent Circle все-равно может видеть, откуда к ним подключаются пользователи по другим параметрам.
Плюсы:
Минусы:
Звонки через бесплатный сервис голосовой связи: Signal (RedPhone)
Услуги защиты разговоров компании Open Whisper Systems также набрали большую популярность после скандала с массовым прослушиванием разговоров спецслужбами мировых держав. Голосовая связь работает через сервера Open Whisper Systems к которым подключаются пользователи с помощью приложения Signail (iOS) и RedPhone (Android). Используется такая же технология защиты разговоров как у BlackBox и Silent Phone.
Принцип работы:
На заметку!
Итого
Сводная таблица плюсов и минусов каждого из способов защиты разговоров оценена по пятибалльной шкале:
Защита от | BlackBox | Silent Phone | RedPhone |
Прослушивания спецслужбами | ✪✪✪✪✪ | ✪✪✪✪ | ✪✪✪✪ |
Прослушивания злоумышленниками | ✪✪✪✪✪ | ✪✪✪✪✪ | ✪✪✪✪ |
«Посредников» в связи | ✪✪✪✪✪ | ✪✪✪ | ✪✪✪ |
Подмены базовой станции мобильной связи | ✪✪✪✪✪ | ✪✪✪✪✪ | ✪✪✪✪✪ |
Идентификации звонящего | ✪✪✪✪✪ | ✪✪ | ✪✪✪ |
Определения местоположения звонящего | ✪✪✪✪ | ☆ | ☆ |
Троянского ПО на смартфоне | ☆ | ☆ | ☆ |
Из представленной таблицы отчетливо видно:
Рекомендация
Кто дорожит конфиденциальностью своих разговоров, но не силен в вопросах информационной безопасности мобильных устройств и не хочет постоянно переживать, заражен ли его смартфон вирусами, троянами-шпионами или клавиатурными шпионами, могут воспользоваться рекомендациями, позволяющими без глубоких знаний защитить свое устройство.
Суть рекомендаций сводиться к не сложным для выполнения шагам:
На этом все. Желаю, чтобы ваши разговоры всегда оставались в сохранности.
Понравилась статья? Подпишитесь на канал, чтобы быть в курсе самых интересных материалов
System.Threading.Channels — высокопроизводительный производитель-потребитель и асинхронность без аллокаций и стэк дайва
И снова здравствуй. Какое-то время назад я писал о другом малоизвестном инструменте для любителей высокой производительности — System.IO.Pipelines. По своей сути, рассматриваемый System.Threading.Channels (в дальнейшем «каналы») построен по похожим принципам, что и Пайплайны, решает ту же задачу — Производитель-Потребитель. Однако имеет в разы более простое апи, которое изящно вольется в любого рода enterprise-код. При этом использует асинхронность без аллокаций и без stack-dive даже в асинхронном случае! (Не всегда, но часто).
Оглавление
Введение
Задача Производитель/Потребитель встречается на пути программистов довольно часто и уже не первый десяток лет. Сам Эдсгер Дейкстра приложил руку к решению данной задачи — ему принадлежит идея использования семафоров для синхронизации потоков при организации работы по принципу производитель/потребитель. И хотя ее решение в простейшем виде известно и довольно тривиально, в реальном мире данный шаблон (Производитель/Потребитель) может встречаться в гораздо более усложненном виде. Также современные стандарты программирования накладывают свои отпечатки, код пишется более упрощенно и разбивается для дальнейшего переиспользования. Все делается для понижения порога написания качественного кода и упрощения данного процесса. И рассматриваемое пространство имен — System.Threading.Channels — очередной шаг на пути к этой цели.
Какое-то время назад я рассматривал System.IO.Pipelines. Там требовалось более внимательная работа и глубокое осознание дела, в ход шли Span и Memory, а для эффективной работы требовалось не вызывать очевидных методов (чтобы избежать лишних выделений памяти) и постоянно думать в байтах. Из-за этого программный интерфейс Пайплайнов был нетривиален и интуитивно не понятен.
В System.Threading.Channels пользователю представляется гораздо более простое api для работы. Стоит упомянуть, что несмотря на простоту api, данный инструмент является весьма оптимизированным и на протяжении своей работы вполне вероятно не выделит память. Возможно это благодаря тому, что под капотом повсеместно используется ValueTask, а даже в случае реальной асинхронности используется IValueTaskSource, который переиспользуется для дальнейших операций. Именно в этом заключается весь интерес реализации Каналов.
Каналы являются обобщенными, тип обобщения, как несложно догадаться — тип, экземпляры которого будут производиться и потребляться. Интересно то, что реализация класса Channel, которая помещается в 1 строку (источник github):
Таким образом основной класс каналов параметризован 2 типами — отдельно под канал производитель и канал потребитель. Но для реализованых каналов это не используется.
Для тех, кто знаком с Пайплайнами, общий подход для начала работы покажется знакомым. А именно. Мы создаем 1 центральный класс, из которого вытаскиваем отдельно производителей(ChannelWriter) и потребителей(ChannelReader). Несмотря на названия, стоит помнить, что это именно производитель/потребитель, а не читатель/писатель из еще одной классической одноименной задачи на многопоточность. ChannelReader изменяет состояние общего channel (вытаскивает значение), которое более становится недоступно. А значит он скорее не читает, а потребляет. Но с реализацией мы ознакомимся позже.
Начало работы. Channel
Начало работы с каналами начинается с абстрактного класса Channel и статического класса Channel, который создает наиболее подходящую реализацию. Далее из этого общего Channel можно получать ChannelWriter для записи в канал и ChannelReader для потребления из канала. Канал является хранилищем общей информации для ChannelWriter и ChannelReader, так, именно в нем хранятся все данные. А уже логика их записи или потребления рассредоточена в ChannelWriter и ChannelReader, Условно каналы можно разделить на 2 группы — безграничные и ограниченные. Первые более простые по реализации, в них можно писать безгранично (пока память позволяет). Вторые же ограничены неким максимальным значением количества записей.
Здесь вытекает немного разная природа асинхронности. В неограниченных каналах операция записи всегда будет завершаться синхронно, нет ничего, что могло бы остановить от записи в канал. Для ограниченных каналов ситуация иная. При стандартном поведении (которое можно заменить) операция записи будет завершаться синхронно до тех пор пока в канале есть место для новых экземпляров. Как только канал заполнен, операция записи не завершится, пока не освободится место (после того, как потребитель потребил потребляемое). Поэтому здесь операция будет реально асинхронной со сменой потоков и сопутствующими изменениями (или без смены, что будет описано чуть позже).
Поведения читателей по большей части одинаково — если в канале есть что-то, то читатель просто читает это и завершается синхронно. Если ничего нет, то ожидает пока кто-то что-то запишет.
Статический класс Channel содержит 4 метода для создания вышеперечисленных каналов:
При желании можно указать более точные опции для создания канала, которые помогут оптимизировать его под указанные нужды.
UnboundedChannelOptions содержит 3 свойства, значение которых по умолчанию false:
У него есть 2 свойства:
Пример начала работы с каналами:
Итак, приступим к изучению непосредственно ChannelReader и ChannelWriter, а также интересных деталей реализации. Они все сводятся к асинхронности без выделений памяти с помощью IValueTaskSource.
ChannelReader — потребитель
У данного метода в абстрактном классе есть реализация, которая основана на методах TryRead и WaitToReadAsync. Если опустить все инфраструктурные нюансы (исключения и cancelation tokens), то логика примерно такая — попытаться прочитать объект с помощью TryRead. Если не удалось, то в цикле while(true) проверять результат метода WaitToReadAsync. Если true, то есть данные есть, вызвать TryRead. Если TryRead получается прочитать, то вернуть результат, в противном случае цикл по новой. Цикл нужен для неудачных попыток чтения — в результате гонки потоков, сразу много потоков могут получить завершение WaitToReadAsync, но объект будет только один, соответственно только один поток сможет прочитать, а остальные уйдут на повторный круг.
Однако данная реализация, как правило, переопределена на что-то более завязанное на внутреннем устройстве.
ChannelWriter — производитель
Все аналогично потребителю, так что сразу смотрим методы:
А теперь перейдем к самой интересной части.
Асинхронность без алллокаций
В процессе написания и изучения кода, я осознал, что почти ничего интересного в реализации всех этих операций нет. В общем можно описать так — избегание лишних блокировок с помощью конкурентных коллекций и обильное использование ValueTask, который является структурой, что экономит память. Однако спешу напомнить, что не стоит быстрой заменой проходиться по всем файлам на вашей ПЭВМ и заменять все Task на ValueTask. Он имеет смысл только в случаях, когда операция в большинстве случаев завершается синхронно. Ведь, как мы помним, при асинхронности вполне вероятна смена потока, а значит и стек уже будет не тот, что прежде. Да и вообще, истинный профессионал в области производительности знает — не оптимизируй до возникновения проблем.
Радует одно, в профессионалы я себя записывать не буду, а поэтому самое время разобраться, в чем же секрет написания асинхронного кода без выделений памяти, что на первый взгляд звучит слишком хорошо для правды. Но бывает и такое.
Интерфейс IValueTaskSource
Как заверяет производитель, данную структуру следует использовать лишь очевидно — с ключевым словом await. То есть не следует применять await много раз к одному и тому же ValueTask, использовать комбинаторы, добавлять несколько продолжений и тп. Также не следует получать результат из ValueTask более одного раза. А связано это как раз с тем, что мы пытаемся понять — переиспользованием всего этого добра без выделения памяти.
Я уже упомянул интерфейс IValueTaskSource. Именно он помогает сэкономить память. Делается это с помощью переиспользования самого IValueTaskSource несколько раз для множества задач. Но именно из-за этого переиспользования и нет возможности баловаться с ValueTask.
Итак, IValueTaskSource. Данный интерфейс имеет 3 метода, реализовав которые вы будете успешно экономить память и время на выделении тех заветных байт.
CompareExchange
Имеются также перегрузи с int, long, float, double, IntPtr, object.
Сам метод атомарный, то бишь выполняется без прерываний. Сравнивает 2 значения и, если они равны, выполняет присваивание нового значения в переменную. Решают проблему, когда нужно проверить значение переменной и в зависимости от него изменить переменную.
Допустим, вы хотите инкрементировать переменную, если ее значение меньше 10.
Далее идут 2 потока.
Поток 1 | Поток 2 |
---|---|
Проверяет значение переменной на некоторое условие (то есть меньше ли оно 10), которое срабатывает | — |
Между проверкой и изменением значения | Присваивает переменной значение, не удовлетворяющее условию (например, 15) |
Изменяет значение, хотя не должен, ведь условие уже не соблюдается | — |
При использовании данного метода, вы либо изменяете именно то значение, что хотели, либо не изменяете, получив при этом актуальное значение переменной.
location1 — переменная, значение которой мы хотим поменять. Оно сравнивается с comparand, в случае равенства в location1 записывается value. Если операция удалась, то метод вернет прошлое значение переменной location1. Если же нет, то будет возращено актуальное значение location1.
Если говорить чуть глубже, то существует инструкция языка ассемблера cmpxchg, которая выполняет эти действия. Именно она и используется под капотом.
Stack dive
Рассматривая весь этот код я не раз наткнулся на упоминания «Stack Dive». Это очень крутая и интересная штука, которая на самом деле очень нежелательна. Суть в том, что при синхронном выполнении продолжений мы можем исчерпать ресурсы стека.
Допустим, мы имеем 10000 задач, в стиле
Допустим, первая задача завершает выполнение и этим освобождает продолжение второй, которое мы начинаем тут же выполнять синхронно в этом потоке, то есть забирая кусок стека стек фреймом данного продолжения. В свою очередь, данное продолжение разблокирует продолжение третей задачи, которое мы тоже начинаем сразу выполнять. И так далее. Если в продолжении больше нет await’ов или чего-то, что как-то сбросит стек, то мы просто будем потреблять стековое пространство до упора. Что может вызвать StackOverflow и крах приложения. В рассмотрении кода я упомяну, как с этим борется AsyncOperation.
AsyncOperation как реализация IValueTaskSource
Также AsyncOperation реализует IThreadPoolWorkItem с единственным методом — void Execute() => SetCompletionAndInvokeContinuation(). Метод SetCompletionAndInvokeContinuation как раз и занимается выполнением продолжения. И данный метод вызывается либо напрямую в коде AsyncOperation, либо через упомянутый Execute. Ведь типы реализующие IThreadPoolWorkItem можно забрасывать в тред пул как-то вот так ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false).
Метод Execute будет выполнен тред пулом.
Само выполнение продолжения довольно тривиально.
Продолжение _continuation копируется в локальную переменную, на ее место записывается s_completedSentinel — искусственный объект-марионетка (иль часовой, не знаю, как глаголить мне в нашей речи), который указывает, что задача завершена. Ну а далее локальная копия реального продолжения просто выполняется. При наличии ExecutionContext, данные действия постятся в контекст. Никакого секрета тут нет. Этот код может быть вызван как напрямую классом — просто вызвав метод, инкапсулирующий эти действия, так и через интерфейс IThreadPoolWorkItem в тред пуле. Теперь можно догадаться, как работает функция с выполнением продолжений синхронно.
Первый метод интерфейса IValueTaskSource — GetResult (github).
Метод тривиален. — он сохраняет принятый параметр в _result и сигнализирует о завершении, а именно вызывает метод SignalCompleteion, который довольно интересен.
В данном методе используется все, о чем мы говорили в начале.
В самом начале, если _continuation == null, мы записываем марионетку s_completedSentinel.
Далее метод можно разделить на 4 блока. Сразу скажу для простоты понимания схемы, 4 блок — просто синхронное выполнение продолжения. То есть тривиальное выполнение продолжения через метод, как я описано в абзаце про IThreadPoolWorkItem.
Третий и последний, но самый сложный метод интерфейса IValueTaskSource — OnCompleted (github)
Метод добавляет продолжение, которое выполняется по завершению.
При необходимости захватывает ExecutionContext и SynchronizationContext.
Далее используется Interlocked.CompareExchange, описанный выше, чтобы сохранить продолжение в поле, сравнивая его с null. Напоминаю, что CompareExchange возвращает актуальное значение переменной.
Если сохранение продолжения прошло, то возвращается значение, которое было в переменной до обновления, то есть null. Это означает, что операция еще не завершилась на момент записи продолжения. И тот, кто ее завершит сам со всем разберется (как мы смотрели выше). И нам нет смысла выполнять какие-то дополнительные действия. И на этом работа метода завершается.
Если сохранить значение не получилось, то есть из CompareExchange вернулось что-то кроме null. В этом случае кто-то успел положить значение в быстрее нас. То есть произошла одна из 2 ситуаций — или задача завершилась быстрее, чем мы до сюда дошли, или была попытка записать более 1 продолжения, что делать нельзя.
Таким образом проверяем возвращенное значение, равно ли оно s_completedSentinel — именно оно было бы записано в случае завершения.
Main, before await. Thread id: 1
Created thread for writing with delay, before await write. Thread id: 4
Main, after await (will be processed by created thread for writing). Thread id: 4
Created thread for writing with delay, after await write. Thread id: 4