Поверхностный просмотр «PEP 0492 — Coroutines with async and await syntax» по началу оставил у меня вопрос «Зачем это надо». Сопрограммы удовлетворительно реализуются на расширенных генераторах и на первый взгляд может показаться, что все свелось к замене yield from на await, а декоратора, создающего сопрограмму на async. Сюда можно добавить и возникающее ощущение, что все это сделано исключительно для использования с модулем asyncio.
Но это, конечно же, не так, тема глубже и интереснее.
coroutine
Главное, наверное, это то, что теперь сопрограмма в Python — это специальный объект native coroutine, а не каким-то специальным образом оформленный генератор или еще что-то. Этот объект имеет методы и функции стандартной библиотеки для работы с ним. То есть теперь, это объект, определяемый как часть языка.
await
К сожалению, не нашел в документации и PEP краткое определение для чего введено это новое ключевое слово. Рискну сформулировать его сам: Ключевое слово await указывает, что при выполнении следующего за ним выражения возможно переключение с текущей сопрограммы на другую или на основной поток выполнения.
Соответственно выражение после await тоже не простое, это должен быть awaitable объект.
awaitable object
Есть три варианта awaitable объектов:
- Другая сопрограмма, а именно объект native coroutine. Этот напоминает, и видимо реализовано аналогично случаю, когда в генераторе с помощью yield from вызывается другой генератор.
- Сопрограмма на основе генератора, созданная с помощью декоратора types.coroutine(). Это вариант обеспечения совместимости с наработками, где сопрограммы реализованы на основе генераторов.
- Специальный объект, у которого реализован магический метод __await__, возвращающий итератор. С помощью этого итератора реализуется возврат результата выполнения сопрограммы.
Примера, как написать свой awaitable объект ни в PEP, ни в документации не нашел, во всяком случае на момент написания статьи этого нет. Ниже этот недостаток будет исправлен.)
async
В РЕР десяток абзацев с заголовками «Why ...» и «Why not ...». Почти все они посвящены вопросу, почему это ключевое слово используется так, а не как-то иначе. И действительно, async def смотрится в коде странно, и вызывает размышления на тему а «pythonic way» ли это? С другой стороны, понятно, что хотели какой-то более целостной картины, так как есть еще и async for и async with.
- async def — определяет native coroutine function, результатом вызова которой будет объект-сопрограмма native coroutine, пока еще не запущенная.
- async for — определяет, что итератор используемый в цикле, при получении следующего значения может переключать выполнение с текущей сопрограммы. Объект итератор имеет вместо стандартных магических методов: __iter__ и __next__, методы: __aiter__ и __anext__. Функционально они аналогичны, но как следует из определения, допускают использования await в своем теле.
- async with — определяет, что при входе в контекстный блок и выходе из него может быть переключение выполнения с текущей сопрограммы. Так же, как и в случае с асинхронным генератором, вместо магических методов: __enter__ и __exit__ следует использовать функционально аналогичные __aenter__ и __aexit__.
В определениях, которые даны в PEP написано, что в магических методах может вызываться «асинхронный код». Мне кажется, «переключение выполнения с текущей сопрограммы» более правильный вариант. Использование термина «асинхронный код» может ввести в заблуждение, потому-что «асинхронный код» часто реализуется на функциях обратного вызова, а это немного другая тема.
Примеры на использование асинхронных итераторов и контекст менеджеров в документации и PEP достаточно, usecase в общем-то понятен и все логично. Непонятно только одно — зачем использовать версии магических методов с другими именами, ведь они все равно объявляются с использованием `async def`. Видимо, это что-то, связанное с особенностями реализации, другого объяснения не вижу.
Как это готовить?
Изучение какой-то новой фичи языка или библиотеки быстро упирается в вопрос, как и где это использовать. И более глубокое изучение, на мой взгляд, стоит продолжать уже на практическом примере. Для меня, если тема связана с сопрограммами, асинхронностью и тому подобными вещами, такой практический пример — это написание хеллоуворда, использующего event-driven подход. Формулировка задачи такая: «Вызов функции sleep должен остановить исполнение сопрограммы на определенное время».
Сопрограммы и event-driven прекрасно сочетаются, в другой моей статье более подробно, почему я так считаю. И пример такого рода хорошо нам продемонстрирует почти все возможности и нюансы использования сопрограмм.
Предположим, что мы имеем диспетчер событий, запущенный в основном потоке исполнения, который при возникновении ожидаемых событий вызывает функции обратного вызова. Тогда практическая реализация может быть такой:
- Функция sleep настраивает диспетчер событий на вызов функции обратного вызова через заданный промежуток времени. После этого переключает управление в основной поток исполнения (то есть на диспетчер).
- Переданная в диспетчер функция обратного вызова вызывается по истечении заданного времени. В ней переходит переключение на сопрограмму с передачей ей какой-то полезной информации.
Закодировано это может быть как-то так:
from time import time
from collections import deque
from tornado.ioloop import IOLoop
current = deque()
class sleep(object):
def __init__(self, timeout):
self.deadline = time() + timeout
def __await__(self):
def swith_to(coro):
current.append(coro)
coro.send(time())
IOLoop.instance().add_timeout(self.deadline, swith_to, current[0])
current.pop()
return (yield)
def coroutine_start(run, *args, **kwargs):
coro = run(*args, **kwargs)
current.append(coro)
coro.send(None)
if __name__ == '__main__':
async def hello(name, timeout):
while True:
now = await sleep(timeout)
print("Hello, {}!\tts: {}".format(name, now))
coroutine_start(hello, "Friends", 1.0)
coroutine_start(hello, "World", 2.5)
IOLoop.instance().start()
Как видите, код краткий, достаточно понятный и, кстати, рабочий. Опишу основные моменты в нем:
- В качестве диспетчера событий использован tornado.ioloop.IOLoop комментировать по моему тут особо нечего.
- Класс sleep — реализует awaitable объект, его функция — передать управление в диспетчер событий, предварительно настроив его на вызов callback через заданный промежуток времени.
- Функция обратного вызова определена как замыкание, но в данном случае это не играет никакой роли. Назначение ее — просто переключить выполнение назад на сопрограмму с передачей текущего времени. Переключение выполнения на сопрограмму, производится вызовом ее метода send или метода throw для переключения с выбросом исключения.
- Назначение функции coroutine_start — это создать сопрограмму, вызвав функцию фабрику и запустить ее на выполнение. Первый вызов метода send сопрограммы, обязательно должен быть с параметром None — это запускает сопрограмму
- Сама функция hello тривиальна. Может и так понятно, но думаю стоит уточнить. Эта функция не сопрограмма! Эта функция, которая создает и возвращает сопрограмму ( функция-фабрика), аналогично функциям, создающим и возвращающим генератор.
Развитие этой идеи: «async/await coroutine and event-driven», можно посмотреть по этой ссылке. Оно еще сырое, но кроме продемонстрированного переключения по событию «timeout», реализовано переключение сопрограмм по событиям «I/O ready» и «system sygnal». В качестве демо, есть пример асинхронного echo server.
В заключение
Сопрограммы в том или ином виде были доступны уже достаточно давно, но не было «официальных правил игры с ними». Теперь эти «правила игры» определены и зафиксированы и это станет хорошим поводом более широко использовать в Python методы асинхронного программирования.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (26)
Qualab
18.09.2015 18:35+1Хорошая статья и хороший подход с async/await. Python как всегда всё больше радует с каждой версией.
veveve
19.09.2015 09:19+1>Использование термина «асинхронный код» может ввести в заблуждение, потому-что «асинхронный код» часто реализуется на функциях обратного вызова, а это немного другая тема.
В том-то и вся прелесть реализации асинхронности Python: вместо лапши из callback’ов, вы пишете обычный «синхронный» код, лишь изредка добавляя «async» и «await» в местах блокировок I/O, и получаете полностью асинхронную программу.Alesh
19.09.2015 13:33+2Все верно, хочу лишь заметить, что все предыдущие реализации coroutine в Pythone, будь то реализация на генераторах в asyncio и tornado, etc или реализация на greenlet тоже позволяли писать обычный «синхронный» код без коллбеков, и даже без изредкого добавления «async» и «await».
niamster
23.09.2015 02:46Если честно, то конструкция await/async не слишком выразительна. Какая-то мешанина из обработчиков и генераторов.
На практике я python3 не использую, потому мои наблюдения могут не иметь смысла.
Попытался сочинить что-то идя по вашим стопам и докам в сети, получилось такое:
import asyncio async def hello(name, timeout): await poke(name, timeout) class poke: def __init__(self, name, timeout): self.name = name self.timeout = timeout def __await__(self): yield from asyncio.sleep(self.timeout) # say hello if not self.name.startswith("world"): yield from asyncio.wait([hello("world.{}".format(self.name), 0.3)]) yield async def friends(): await asyncio.wait([ hello("friends", 0.5), hello("neighbours", 0.3), ]) loop = asyncio.get_event_loop() loop.run_until_complete(friends())
Вроде все понятно. А теперь я решил включить мозги и попробовать без asyncio:
import time async def hello(name, timeout): await poke(name, timeout) class poke: def __init__(self, name, timeout): self.name = name self.timeout = timeout def __await__(self): time.sleep(self.timeout) # say hello if not self.name.startswith("world"): coro = hello("world.{}".format(self.name), 0.3) while True: try: coro.send(None) yield except StopIteration: break yield async def friends(): coros = [ hello("friends", 0.5), hello("neighbours", 0.3) ] for coro in coros: coro.send(None) for coro in coros: await coro poll = friends() while True: try: poll.send(None) except StopIteration: break
Второй вариант выглядит коряво и асимметрично. В чем дело? Так и должно быть?
Почему нельзя вызывать `await` в `__await__` методе? Почему для `friends` я также не могу вызвать `await`, но должен в цикле слать `send`?
Также я заметил, что порядок выполнения разный, хотя все стадии проходит в обоих случаях. Если честно, то не совсем понятно почему так получается.Alesh
23.09.2015 10:47Не обижайтесь, но вы либо совсем ничего не поняли, либо ваш опыт использования кода на callback, был категорически против загрузки в голову :) материала про coroutine. Видно четко, что вы не поняли преимуществ, которые дает использование сопрограмм и пытались притянуть в примеры концепцию функций обратного вызова. Я сейчас прокомментирую ваш код с asyncio и дам свой вариант.
- Сопрограмма созданная вызовом hello у вас завершается не успев сделать чего-то существенного, кроме ожидания своего завершения.
- Зачем вы создаете и запускаете другую сопрограмму в await объекте?
- Добавление «world.», может я не понял идеи. Если это для ограничение итераций, то подход абсолютно неверный — в самой короутине это можно и нужно сделать.
- Функции asyncio такие как sleep, wait, etc. уже возвращают awaitable объект. Использовать с ними yield from не надо.
- def friends() — это просто неоправданное увеличение энтропии :)
import asyncio async def hello(name, timeout): cnt = 0 while True and cnt < 5: await asyncio.sleep(timeout) print("Hello, {}".format(name)) cnt += 1 if __name__ == '__main__': tasks = [ hello("friends", 0.5), hello("neighbours", 0.3), ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) loop.close()
В втором варианте, к проблемам первого еще и добавляется event-driven. Вам надо почитать больше про событийное программирование. Предыдущая моя статья немного этого касалась. А как пример, ну я уже приводил пример в статье без asyncio. Единственно могу сделать пример без tornado loop со своим простейшим диспетчером событий. Но это если интересно напишите, отдельным постом сделаю.niamster
23.09.2015 15:22Спасибо за ответ.
Не обижайтесь, но вы либо совсем ничего не поняли, либо ваш опыт использования кода на callback, был категорически против загрузки в голову :) материала про coroutine. Видно четко, что вы не поняли преимуществ, которые дает использование сопрограмм и пытались притянуть в примеры концепцию функций обратного вызова.
Я не обижаюсь, а пытаюсь разгрести кашу в голове. С coroutine я знаком — libevent в C, Fiber в ruby — в данных случаях у меня не возникало проблем с пониманием. В вашем примере сложно отследить что за чем следует. Да, если использовать coroutine_start и закрыть глаза на все остальное — ничего сложного. К стати не совсем понятно зачем `current.append(coro)` в `switch_to` — чтоб поддерживать бесконечный цикл в `hello`? Зачем там вообще бесконечный цикл? Если убрать бесконечный цикл, то обьект sleep не будет функционировать правильно(список current всегда будет содержать лишние `hello`) и, если я правильно понимаю, в определенный момент выскочит исключение.
В своем примере я попытался опробовать coroutine generator и вложенные вызовы этих же coroutine.
Сопрограмма созданная вызовом hello у вас завершается не успев сделать чего-то существенного, кроме ожидания своего завершения.
Ну как же — запускает coroutine `poke`. Да, не возвращает результат, но это пример сказать «привет» — считайте keep-alive.
Зачем вы создаете и запускаете другую сопрограмму в await объекте?
Возможно пример получился неудачный, но в данном случае я хотел просто вызвать еще один await, почему бы и нет?
Добавление «world.», может я не понял идеи. Если это для ограничение итераций, то подход абсолютно неверный — в самой короутине это можно и нужно сделать.
В данном случае чтоб избежать бесконечной рекурсии. Да, лучше бы вызвал не hello, а что-то другое.
Функции asyncio такие как sleep, wait, etc. уже возвращают awaitable объект. Использовать с ними yield from не надо.
Если я правильно понимаю, то в `__await__` можно использовать либо `yield from` либо `return`. Python3.5 не разрешает использовать `await` в `__await__`
def friends() — это просто неоправданное увеличение энтропии :)
Не совсем понятно данное утверждение.
Попытался соорудить более понятный пример, в котором я приглашаю друзей на «вечеринку»:
import asyncio async def say(name, what, timeout): return await poke('{} {}'.format(what, name), timeout) async def ping(timeout): await asyncio.sleep(timeout) return 'OK' async def handshake(timeout): await asyncio.sleep(timeout) return 'OK' class poke: def __init__(self, name, timeout): self.name = name self.timeout = timeout def __await__(self): res = yield from asyncio.wait_for(ping(0.1), None) assert res == 'OK' res = yield from asyncio.wait_for(handshake(0.1), None) assert res == 'OK' return 'OK' async def invite_friends(): res, _ = await asyncio.wait([ say("friends", 'hello', 0.5), say("neighbours", 'hello', 0.3), ]) assert all(([x.result() == 'OK' for x in res])) loop = asyncio.get_event_loop() loop.run_until_complete(invite_friends())
Возможно я не понимаю основное идеи Future-like объектов? В моем случае можно было бы и обойтись `async coroutine`, конечно. Возможно Вы сможете дать наглядный пример использования подобных объектов?
Также все еще интересно увидеть комментарии ко второй части моего оригинального поста.Alesh
24.09.2015 00:40Посмотрел на ваш следующий пример и еще раз вам пишу, вы используете сопрограммы в подходе как использовали бы функции обратного вызова. Зачем? Сформулируйте мне какую задачу реализовываете в примере. Я дам свой пример, возможно это поможет разобраться. А возможно задача у вас вообще не укладывающееся в async/awaite, и пытаясь ее все же решить через coroutine вы приходите к таким странным вещам как сопрограммы делающие только то, что дожидающиеся своего завершения.
`current.append(coro)` — `current[0]` Будет содержать текущую выполняющуюся сопрограмму. В том примере это не совсем нужно, но если бы был запуск другой короутины из исполняющейся без этого было бы не обойтись.
`switch_to` — не поддерживает цикл, а возвращает управление в короутину.
Во втором примере самое трагическое это строка `time.sleep(self.timeout) # say hello` эта строка останавливает выполнение Python кода полностью. Соответственно ни о какой кооперативной многозадачности речи уже не идет.
Я думаю смысла разбираться с тем нет, пока не разберемся с назначением сопрограммы. В примере с asyncio хотя бы диспетчер событий и awaitable объекты уже готовы.
niamster
24.09.2015 01:19Посмотрел на ваш следующий пример и еще раз вам пишу, вы используете сопрограммы в подходе как использовали бы функции обратного вызова. Зачем? Сформулируйте мне какую задачу реализовываете в примере. Я дам свой пример, возможно это поможет разобраться. А возможно задача у вас вообще не укладывающееся в async/awaite, и пытаясь ее все же решить через coroutine вы приходите к таким странным вещам как сопрограммы делающие только то, что дожидающиеся своего завершения.
Замените asyncio.sleep на что-то вроде loop.sock_sendall+loop.sock_recv. Так лучше понятна задача? Использовал asyncio.sleep для эмуляции задержек в качестве примера.
Задача такова — есть список друзей, нужно всех пригласить на праздник. Чтоб пригласить на праздник сначала нужно дозвониться(ping), потом уговорить прийти(handshake). Представим, что я могу звонить всем одновременно, и пока один тупит — могу говорить с другим. Пока говорю с одним — все ждут. Собственно под этим я и понимаю coroutines в single-threaded event loop.
Почему я не могу использовать coroutines в данном примере? Как бы вы реализовали эту задачу?
Возможно необходимо было использовать `s/say/invite/`чтоб было более очевидно, согласен.
Ради наглядности переписал бы как-то так:
import asyncio import logging from random import random logging.getLogger().setLevel(logging.DEBUG) class dialog: def __init__(self, name, latency): self.name = name self.latency = latency async def call(self): logging.debug('calling {}'.format(self.name)) await asyncio.sleep(self.latency/2+random()) return 'OK' async def convince(self): logging.debug('convincing {}'.format(self.name)) await asyncio.sleep(self.latency/2+random()) return 'OK' def __await__(self): res = yield from asyncio.wait_for(self.call(), None) assert res == 'OK' res = yield from asyncio.wait_for(self.convince(), None) assert res == 'OK' logging.debug('invited {}'.format(self.name)) return 'OK' async def invite(name, latency): return await dialog(name, latency) async def invite_friends(): friends = [ # (name, latency) ('mark', 0.5), ('bob', 0.3), ] coros = [invite(name, latency) for name, latency in friends] res, _ = await asyncio.wait(coros) assert all(([x.result() == 'OK' for x in res])) loop = asyncio.get_event_loop() loop.run_until_complete(invite_friends())
Alesh
24.09.2015 11:22Почему я не могу использовать coroutines в данном примере?
Я не говорил, что в этой задачи нельзя использовать coroutine, не внимательно прочитали мой ответ?
Как бы вы реализовали эту задачу?
Задачу вы описали, я ее понял, и мой пример будет ниже. Теперь мне и стало понятно что у вас не так. Зачем делать логику, тем более прикладную в awaitable объекте? Он для этого не предназначен. Делайте всю логику в сопрограммах. Пример ниже, возможно многословный, но просто хотелось красивый лог:
import random import logging import asyncio async def call_to(name): cnt = 0 max_ring = 7 result = False logging.debug("Calling {} ...".format(name)) attempts = random.randrange(0, 9, 1) + 1 while cnt < attempts: await asyncio.sleep(1.0) logging.debug("({}): beep".format(name)) cnt += 1 if cnt == max_ring: logging.debug("({}): not picked up".format(name)) break else: result = True return result async def sell_on(name): cnt = 0 max_offer = 3 logging.debug("Responding {} ...".format(name)) while True: cnt += 1 await asyncio.sleep(1.0) answer = random.randrange(0, 3, 1) if answer == 2: logging.debug("({}): Yes, I will come".format(name)) return True elif answer == 1: logging.debug("({}): No, I will not come".format(name)) return False else: if cnt == max_offer: logging.debug("({}): No, I will not come".format(name)) return False else: logging.debug("({}): Maybe, I don't know".format(name)) async def invite(name, result): answered = await call_to(name) if answered: agreed = await sell_on(name) result.append((name, agreed)) else: result.append((name, answered)) if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) result = list() frends = ['Саша', 'Паша', 'Катя', 'Маша', 'Дуся', 'Маруся', 'Ваня'] tasks = [invite(name, result) for name in frends] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) print("\n----------------------------------------") for name, agreed in result: print("{}\t{}".format(name, "придет" if agreed else "не придет")) loop.close()
Alesh
24.09.2015 11:32Можно sell_on переписать более интересно:
async def sell_on(name): cnt = 0 max_offer = 3 logging.debug("Responding {} ...".format(name)) while True: cnt += 1 answer = random.randrange(0, 9, 1) + 1 await asyncio.sleep(answer) if answer % 2: logging.debug("({}): Yes, I will come".format(name)) return True else: logging.debug("({}): No, I will not come".format(name)) return False
niamster
24.09.2015 22:02Спасибо за развернутый ответ.
Грубо говоря тоже самое, что и в моем примере, только без future-like object.
Я так понял наше недопонимание возникло из-за этого самого furure-like object. Как я упоминал ранее — хотел на самом деле пощупать что оно такое. Исходя из ваших ответов, я не представляю что это за фрукт но все-таки хотелось бы понять суть awaitable объекта и почему по вашему мнению он здесь не клеится.Alesh
24.09.2015 22:28Мне кажется, ну собственно и в документации awaitable объект упоминается и применяется только качестве, специализированного объекта переключающего управление с/на сопрограмму с возвратом или нет результата выполнения асинхронного действия. Во всяком случае примеров нет, которые бы более полно или както по другому раскрыли его назначение.
Зачем нагружать его еще какой-то логикой, если это удобнее и логичнее сделать в суб. сопрограмме вызываемой из текущей сопрограммы с помощью ключевого слова await. Здесь логика похожа на вложенные генераторы, вызываемые с помощью yield from, вернее не похоже, а по внутренней реализации тоже самое.niamster
24.09.2015 22:58В том то и дело, что примеров нет.
Осталось только услышать ответ эксперта по поводу использования голого async/await без сторонних библиотек типа tornado(ok, asyncio встроен в python, но он же не реализует все на свете). Собственно меня смущает как coroutine запускается(см. второй кусок кода из моего первого комментария). С time.sleep понятно(хотя не логично — могли бы допилить), но вызов в цикле который прерывается по исключению StopIteration — как по мне либо выглядит убого.
На вопрос зачем — а вдруг я не хочу тянуть весь asyncio просто потому, что хочу баловаться python на устройстве с ограниченными ресурсами.Alesh
24.09.2015 23:18Осталось только услышать ответ эксперта по поводу использования голого async/await без сторонних библиотек
Окей) Что нам дает asyncio, из того что мы использовали в своих примерах? Оно нам дает event loop (диспетчер событий) и функцию sleep, которая возвращает awaitable. Этот awaitable делает следующее, переключает управление на event loop предварительно каким-то образом наладив event loop на возврат управления в текущую сопрограмму через заданный промежуток времени.
В примере, в статье, я использовал event loop tornado, а awaitable объект написал сам. Можем его разобрать если не вполне понятно, что там происходит.
Как писать диспетчер, я приводил пример в предыдущей статье, но в принципе тема event loop не имеет прямого отношения к coroutine ибо с таким же успехом используется в асинхронных программах построенных на функциях обратного вызова.
Alesh
24.09.2015 23:23Насчет «не хочу тянуть весь asyncio», да из него слепили монстра на все случаи жизни. Хотя в большинстве своем достаточно event loop и трех awaitable объектов типо sleep, wait_io, wait_signal. Я как раз собираюсь исправить этот недостаток asyncio :) если не потеряю интерес и мотивацию.
niamster
24.09.2015 23:34Надеюсь не потеряете. Тема для python интересная, а по сути кроме asyncio никакой «легковесной» альтернативы или примеров реализации в сети нет =/
baldr
А как обстоит дело с потокобезопасностью в вашем примере? Например, объект 'current' — я правильно понимаю что вы просто опустили синхронизацию для упрощения?
Alesh
Никак, все происходит в одном потоке. Прелесть сопрограмм в том что они хорошо реализуют событийно-управляемую многозадачность (вытесняемую многозадачность) в одном потоке. Синхронизации нет, так как нечего синхронизировать, current нужно для отслеживания текущей (выполняющейся в данный момент) сопрограммы.
RusSuckOFF
Вытесняемая многозадачность, события и один поток в одном предложении, как мне кажется, образуют какую-то кашу. Под вытеснением обычно же понимают тот факт, что поток исполнения может переключится не по своему желанию. В этом же случае если сопрограмма написана криво, то она захватит поток исполнения навечно, разве нет?
Alesh
Безусловно, в скобочках должно быть написано (кооперативная многозадачность). Размышлял когда писал пост, почему оппоненту показалось что в примере задействованы потоки и выдал другое название обычной многопоточной многозадачности.
kekekeks
Вообще говоря это не вытесняющая, а кооперативная многозадачность. Есть event-loop, который шедулит сопрограммы. Ситуации, когда посреди выполнения синхронного кода (без await) будет переключен контекст на другую сопрограмму случиться не может.
Alesh
Вообще-то постом выше и раньше вашего я признал свою ошибку и даже попытался ее объяснить.
zaz600
т.е. всегда необходимо запускать луп и на нем всегда выполнение основного кода завершается? в том смысле, что после него нет смысла чего-нибудь выполнять.
как запускать сопрограмму выполняющую периодически какое-либо действие? например, сканирующую каталоги и обновляющую какой-нибудь массив, но при этом другая сопрограмма должна что-то с этим массивом делать?
вообще, какое применение данной функциональности может быть, если все выполняется в одном потоке?:) ну, кроме tcp-клиента или ожидания расчета факториала:)
Alesh
zaz600
спасибо