Согласно этой последней версии C ++ TS: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4628.pdf, и, основываясь на понимании поддержки языка асинхронной / ожидающей C #, мне интересно, что такое «контекст выполнения» (терминология, заимствованная из C #) сопрограмм C ++?
Мой простой тестовый код в Visual C ++ 2017 RC показывает, что сопрограммы, кажется, всегда выполняются в потоке пула потоков, и разработчику приложения предоставляется небольшой контроль над тем, в каком контексте потока могут быть выполнены сопрограммы — например, Может ли приложение принудительно выполнить все сопрограммы (с созданным компилятором кодом конечного автомата) только в основном потоке, без с участием любого потока пула потоков?
В C # SynchronizationContext — это способ указать «контекст», в котором все «половинки» сопрограммы (код конечного автомата, сгенерированный компилятором) будут размещены и выполнены, как показано в этом сообщении: https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/, в то время как текущая реализация сопрограммы в Visual C ++ 2017 RC, кажется, всегда полагается на среду выполнения параллелизма, которая по умолчанию выполняет сгенерированный код конечного автомата в потоке пула потоков. Существует ли аналогичная концепция контекста синхронизации, которую пользовательское приложение может использовать для привязки выполнения сопрограммы к определенному потоку?
Кроме того, каково текущее поведение сопрограмм по умолчанию для «планировщика», реализованное в Visual C ++ 2017 RC? т.е. 1) как точно указано условие ожидания? и 2) когда условие ожидания выполнено, кто вызывает «нижнюю половину» приостановленной сопрограммы?
Мое (наивное) предположение относительно планирования задач в C # состоит в том, что C # «реализует» условие ожидания исключительно путем продолжения задачи — условие ожидания синтезируется задачей, принадлежащей TaskCompletionSource, и любая логика кода, которая должна ждать, будет связана как продолжение это так, если условие ожидания удовлетворяется, например, если полное сообщение получено от низкоуровневого сетевого обработчика, он выполняет TaskCompletionSource.SetValue, который переводит базовую задачу в завершенное состояние, эффективно позволяя цепочечной логике продолжения начать выполнение (перевод задачи в состояние / список готовности из предыдущее созданное состояние) — в сопрограмме C ++ я предполагаю, что std :: future и std :: обещание будут использоваться в качестве аналогичного механизма (std :: future — это задача, а std :: обещание — это TaskCompletionSource и использование тоже удивительно похожи!) — так ли планировщик сопрограмм C ++, если таковой имеется, полагается на некоторый подобный механизм для выполнения поведения?
[EDIT]: после некоторых дальнейших исследований я смог написать очень простую, но очень мощную абстракцию, называемую awaitable, которая поддерживает однопотоковую и кооперативную многозадачность и имеет простой планировщик на основе thread_local, который может выполнять сопрограммы в потоке корень сопрограмма запущена. Код можно найти в этом репозитории github: https://github.com/llint/AwaitableAwaitable может быть составлен таким образом, что он поддерживает правильное упорядочение вызовов на вложенных уровнях, и он имеет примитивную выдачу, синхронизированное ожидание и настройку, готовые откуда-то еще, и из этого может быть получен очень сложный шаблон использования (такой как бесконечные циклические сопрограммы, которые только просыпаться, когда происходят определенные события), модель программирования тесно следует шаблону асинхронного / ожидающего выполнения C # Task. Пожалуйста, не стесняйтесь оставлять свои отзывы.
Противоположный!
Сопрограмма C ++ — это все о контроле. ключевым моментом здесь является
void await_suspend(std::experimental::coroutine_handle<> handle)
функция.
Иви co_await
ожидает ожидаемого типа. Короче говоря, ожидаемый тип — это тип, который обеспечивает следующие три функции:
bool await_ready()
— должна ли программа остановить выполнение сопрограммы?void await_suspend(handle)
— программа передает вам контекст продолжения для этого фрейма сопрограммы. если вы активируете ручку (например, позвонив operator ()
что дескриптор обеспечивает — текущий поток немедленно возобновляет сопрограмму).T await_resume()
— сообщает потоку, который возобновляет сопрограмму, что делать при возобновлении сопрограммы и что возвращать с co_await
,поэтому, когда вы звоните co_await
на ожидаемом типе, программа спрашивает ожидающего, если сопрограмма должна быть приостановлена (если await_ready
возвращает false) и если да — вы получите ручку сопрограммы, в которой вы можете делать все что угодно.
например, вы можете передать дескриптор сопрограммы в пул потоков. в этом случае поток пула потоков возобновит сопрограмму.
Вы можете передать ручку сопрограммы к простому std::thread
— ваш своя Создать поток возобновит сопрограмму.
вы можете прикрепить дескриптор сопрограммы в производный класс OVERLAPPED
и возобновить сопрограмму после завершения асинхронного ввода-вывода.
как вы можете видеть — вы можете контролировать, где и когда сопрограмма приостанавливается и возобновляется — управляя ручкой сопрограммы, переданной в await_suspend
, «Планировщик по умолчанию» отсутствует — как вы реализуете свой ожидаемый тип, будет зависеть от того, как будет планироваться сопрограмма.
Итак, что происходит в VC ++? к несчастью, std::future
до сих пор нет then
функция, так что вы не может передать ручку сопрограммы std::future
, если вы ждете std::future
— программа просто откроет новую тему. посмотрите на исходный код, заданный future
заголовок:
template<class _Ty>
void await_suspend(future<_Ty>& _Fut,
experimental::coroutine_handle<> _ResumeCb)
{ // change to .then when future gets .then
thread _WaitingThread([&_Fut, _ResumeCb]{
_Fut.wait();
_ResumeCb();
});
_WaitingThread.detach();
}
Так почему же вы видели поток потоков win32, если сопрограммы запускаются в обычном режиме? std::thread
? это потому что это не была сопрограмма. std::async
за кулисами concurrency::create_task
, concurrency::task
по умолчанию запускается под пулом потоков win32. в конце концов, вся цель std::async
это запустить вызываемый в другом потоке.
Других решений пока нет …