Russian Qt Forum
Ноябрь 27, 2024, 07:07 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 4   Вниз
  Печать  
Автор Тема: Корутины (модное слово)  (Прочитано 24612 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Сентябрь 24, 2020, 13:47 »

Добрый день

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

Цитировать
Перепишем код с использованием корутины и suspend функции:
   
launch {
    val url = buildUrl()
 
    download(url) //suspend function
 
    toast("File is downloaded")
}

Эта корутина не заблокирует поток, в котором будет запущен ее код. Т.е. его можно запустить даже в main потоке. Функция download загрузит файл в отдельном потоке, а toast будет выполнен только после того, как download отработает.
Ну ясно, download выполняется в др нитке. Но как это "не заблокирует поток" Непонимающий Если toast должен быть выполнен строго позже download, то надо либо(синхронно)  ждать завершения download (и значит нитка стоит) - или (асинхронно) отдать упр-е в событийный цикл, имея ввиду что download "в процессе".

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

Поясните. Спасибо


Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #1 : Сентябрь 24, 2020, 14:52 »

Думаю, те, кто "корутины" придумали, про слово "поток" и "процесс" в лучшем случае когда-то на 1 курсе слышали.
Потоки это же "очень сложно", ведь там есть "мютексы" (а это по уровню ужаса почти как "поинтеры").
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #2 : Сентябрь 24, 2020, 15:07 »

Думаю, те, кто "корутины" придумали, про слово "поток" и "процесс" в лучшем случае когда-то на 1 курсе слышали.
Потоки это же "очень сложно", ведь там есть "мютексы" (а это по уровню ужаса почти как "поинтеры").


Корутины придумали чтобы не превращать код в лапшу коллбеков или хендлеров эвентов (то, на что Игорс жалуется в соседней теме).
Вместо подписывания на сигнал readyRead, мы просто делаем асинхронный await device->bytesAvailable().
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #3 : Сентябрь 24, 2020, 15:17 »

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

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

Поясните. Спасибо

Когда вы пишите (на плюсах) download(url) - это синхронный вызов функции.
Когда вы пишеште (на плюсах) co_await download(url) - это асинхронный вызов корутины.
Ну или на питоне
Код:
async def download(url):
     ...

await download(url)
Ключевое тут то что download - это не функция, а корутина. Типа как функтор это не функция так и корутина это отдельная сущность. Корутины умеют сохранять свое текущее состояние и возобновлять исполнение позже.
Ключевой вопрос в том, что происходит, когда корутина сохраняет свое состояние. В с++20 на это ответа нет - можно (многословно) написать свои таски и промисы которые как-то осуществляют диспатч.
Типичный пример диспатча (например, await в питоне) - это возврат в эвентлуп. Когда вы делаете co_await device->bytesAvailable(), корутина отдает управление в эвентлуп, этот луп обрабатывает события; когда придет событие нашей корутины, управление передастся ей и она продолжит выполнение.
Можно и по-другому диспатчить, например, в разных тредах - см. пример https://en.cppreference.com/w/cpp/language/coroutines.
На практике я знаю 2 вида диспатча - это генераторы (когда корутина синхронно отдает последовательность значений) и возврат в эвентлуп.
О5 же cppref подсказывает что можно делать "ленивые" корутины - типа генератор одного значения. Ну окей, неясно зачем отдельная сущность но пусть будет.
« Последнее редактирование: Сентябрь 24, 2020, 15:21 от Авварон » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #4 : Сентябрь 25, 2020, 13:41 »

Ключевой вопрос в том, что происходит, когда корутина сохраняет свое состояние. В с++20 на это ответа нет - можно (многословно) написать свои таски и промисы которые как-то осуществляют диспатч.
Типичный пример диспатча (например, await в питоне) - это возврат в эвентлуп. Когда вы делаете co_await device->bytesAvailable(), корутина отдает управление в эвентлуп, этот луп обрабатывает события; когда придет событие нашей корутины, управление передастся ей и она продолжит выполнение.
Можно и по-другому диспатчить, например, в разных тредах - см. пример https://en.cppreference.com/w/cpp/language/coroutines.
На практике я знаю 2 вида диспатча - это генераторы (когда корутина синхронно отдает последовательность значений) и возврат в эвентлуп.
Спасибо за разъяснения, понимаю что фичв "в процессе" и полной ясности ожидать не приходится. Пока больше ничего не читал, просто немного поразмышлял (ну правда без карандаша и блокнота  Улыбающийся).

Если это фича поддерживаемая стандартной библиотекой то видимо "eventLoop" в ней участвовать не должен, т.к. либа о нем ничего не знает. Тогда не вижу др способа кроме такого
Код
C++ (Qt)
void Test( void )
{
 InitData();
 SomeCoRoutine()
 AnalyzeResults();
}
При выполнении этого кода main добирается до SomeCoRoutine, запускает задачу и... выходит из Test, ничего др разумного не видно. Когда задача отработала - main каким-то чудесным образом (пока неясно каким) возвращается на AnalyzeResults. Здесь неясно а что же с тем кодом что main молотила до волшебного возврата.

Насколько верны мои наивные предположения?  Улыбающийся
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #5 : Сентябрь 25, 2020, 15:04 »

Спасибо за разъяснения, понимаю что фичв "в процессе" и полной ясности ожидать не приходится.
В каком процессе?  Смеющийся

Сопрограммы описывал еще Кнут в своей книге в 70-х годах прошлого века.

Библиотеки для "зеленых тредов" (без сохранения/восстановления контекста) были в C десятки лет.

Поддержка сохранения/восстановления контекста кучу лет доступно в бусте (boost.context), также есть поддержка в linux (man ucontext). У буста есть основаная на их context поддержка fiber (boost.fiber). Используя эти средства можно писать свои корутины с самыми замысловатыми планирощиками. Уже кучу лет в boost.asio есть возможность писать асинхронный код с использованием корутин, что действительно дает более читаемый и понятный код, по сравнению с колбечным.

Сейчас их просто добавили в стандарт. Будем надеятся у них это получиться хорошо. Улыбающийся
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #6 : Сентябрь 25, 2020, 15:20 »

При выполнении этого кода main добирается до SomeCoRoutine, запускает задачу и... выходит из Test, ничего др разумного не видно. Когда задача отработала - main каким-то чудесным образом (пока неясно каким) возвращается на AnalyzeResults. Здесь неясно а что же с тем кодом что main молотила до волшебного возврата.

Насколько верны мои наивные предположения?  Улыбающийся

Вся магия в операторе co_await который вы зачем-то опускаете. По факту это такой return из корутины с возможностью потом вернуться. Когда вы делаете co_await myCoroutine(), управление возвращается наверх создавая объект корутины который хранит стейт. Когда кто-то где-то позовет h.resume() (где h - хэндл корутины, std::coroutine_handle), управление передастся обратно этому коду и оно продолжится после co_await.
Вот чувак поигрался с co_await кутешных сигналов.

Я добавил отладки чтобы было понятно куда идет control flow:
Код:
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // a really simple widget
    ColorRect cr;
    cr.setWindowTitle("Color Cycler");
    cr.show();

    qDebug() << "before coro";
    // change widget color every 500ms
    QTimer * changeTimer = new QTimer(&app);
    auto ro = [&]() -> qtcoro::return_object<> {
        qDebug() << "entering coro";
        while (true) {
            qDebug() << "entering loop";
            co_await qtcoro::make_awaitable_signal(changeTimer, &QTimer::timeout);
            qDebug() << "awaited";
            cr.changeColor();
        }
    }();
    qDebug() << "after coro";

    changeTimer->start(5000);

    qDebug() << "entering exec()";

    return app.exec();
}
Выхлоп:
Код:
before coro
entering coro
entering loop
after coro
entering exec()
awaited
entering loop
awaited
entering loop
Как видно, после первого вызова co_await, мы вышли из лямбды и ушли в эвентлуп, когда таймер тикнул, мы вернулись в лябмду, сделали код после co_await и снова ушли в эвентлуп.
С тз Qt это выглядит так - мы спим в эвентлупе, тикает таймер, он дергает сигнал, тот вызывает слот, который делает handle.resume(), передавая управление в лямбду, выполняется лямбда, доходит до co_await, возвращаемся из handle.resume(), возвращаемся в эвентлуп.

Если у вас Xcode 12, то можно поиграться с его примерами, с минимальными изменениями проекта оно собирается с AppleClang
« Последнее редактирование: Сентябрь 25, 2020, 15:59 от Авварон » Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #7 : Сентябрь 25, 2020, 18:58 »

Мда... какие-то адские танцы с бубнами вокруг новообразованных абстракций...
Чем не устраивает асинхронное выполнение в другом потоке?
Ну что то типа такого:

<тут мы в гуе>

auto downloadResult = start_in_another_thread(download(url));

... и пошло где-то там работать ...

show_wait_dialog();

while (!downloadResult.isDone())
   ... загрузка тянется, диалог обновляется ...
  update_wait_dialog(downloadResult.progress());

... тут поток кончился ...

close_wait_dialog();


Конечно, можно это и поэстетичнее оформить. Просто как правило людям нужно нечто простое для понимания и написания, типа там:

auto downloadResult = start_in_another_thread(download(url));  // это в один поток пошло

show_wait_dialog_with_progress(downloadResult);  // а это в другой (гуй), диалог висит и обновляется, пока первый поток жив
« Последнее редактирование: Сентябрь 25, 2020, 19:00 от Racheengel » Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #8 : Сентябрь 25, 2020, 19:05 »

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

Ассинхронное программирование как раз и позволяет ничего не ждать, а использовать поток для полезной работы, которая готова для выполнения.
Корутины же позволяют описывать ассинхронные алгоритмы как синхронные.
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #9 : Сентябрь 25, 2020, 19:09 »

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

Нет, почему же, окошки обновлять, прогресс показывать, на мыш реагировать Улыбающийся
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #10 : Сентябрь 26, 2020, 13:40 »

Может, будет полезно, сегодня на статью наткнулся https://habr.com/ru/post/520756/?utm_source=habrahabr&utm_medium=rss&utm_campaign=520756
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #11 : Сентябрь 26, 2020, 13:41 »

Вся магия в операторе co_await который вы зачем-то опускаете.
По той простой причине что я не знал о его существовании Улыбающийся Выходит он все и делает: запускает указанную задачу в др нитке и выходит из текущего блока кода (в скобарях). И он же гарантирует что по окончании задачи упр-е (в main) получит строка после вызова co_await.

Интересен момент обратной передачи упр-я, т.е. возврат на точку после co_await по окончании задачи. Ведь main может быть занят какой-то полезной работой (для этого все и городилось), и просто так прервать эту работу нельзя

Чем не устраивает асинхронное выполнение в другом потоке?
Ну что то типа такого:

<тут мы в гуе>

auto downloadResult = start_in_another_thread(download(url));

... и пошло где-то там работать ...

show_wait_dialog();

while (!downloadResult.isDone())
   ... загрузка тянется, диалог обновляется ...
  update_wait_dialog(downloadResult.progress());

... тут поток кончился ...

close_wait_dialog();
Типичный пример "бездумного запуска в др потоке" Улыбающийся Если мы все равно "ждем-с", то надо было в main и выполнять, периодически вызывая processEvents, чем кстати и занимается QProgressDialog
Записан
Пантер
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #12 : Сентябрь 26, 2020, 14:00 »

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

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #13 : Сентябрь 26, 2020, 14:04 »

Igors, я советую тебе взять Python3 и на его примере изучить, что такое асинхронное программирование. В плюсах как всегда все переусложнено.
И я вот не понимаю, тебе лень открыть документацию и почитать? Я думаю, даже викиучебника будет достаточно чтобы схватить основную идею, там все примитивно.
Если я "схватил основную идею", то мне не составляет труда ее пояснить. А Вам?  Улыбающийся
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #14 : Сентябрь 26, 2020, 14:06 »

Типичный пример "бездумного запуска в др потоке" Улыбающийся Если мы все равно "ждем-с", то надо было в main и выполнять, периодически вызывая processEvents, чем кстати и занимается QProgressDialog

Нет никаких ниток, всё в одном потоке делается.
Считайте что co_await делает внутри setjump/longjump в разные куски программы, такой goto. Как оно "у ей внутре" устроено - вопрос отдельный, но логика примерно такая - она как-то сохраняет текущий фрейм и позволяет к нему вернуться.
+1 за питон - взять и написать простейшую качалку файлов на корутинах, задача часа на 2 из них час-полтора занимает чтение что такое корутины в питоне.
Я уже приводил пример, последовательный блокирующий код
Код:
def foo():
    ...

a = foo()
b = bar(a)
c = baz(b)

тривиально превращается в асинхронный:
Код:
async def foo():
    ...

a = await foo()
b = await bar(a)
c = await baz(b)
« Последнее редактирование: Сентябрь 26, 2020, 14:08 от Авварон » Записан
Страниц: [1] 2 3 4   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.082 секунд. Запросов: 23.