{"id":108634,"date":"2023-08-22T07:00:00","date_gmt":"2023-08-22T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108634"},"modified":"2023-08-22T07:12:32","modified_gmt":"2023-08-22T14:12:32","slug":"20230822-00-2","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230822-00\/?p=108634","title":{"rendered":"On writing loops in PPL and continuation-passing style, part 1"},"content":{"rendered":"<p>The Parallel Patterns Library (PPL) is based on a continuation-passing style, where you invoke a task, and then attach a callable object that will be given the result. Prior to the introduction of the <code>await<\/code> and <code>co_await<\/code> keywords to C#, JavaScript, and C++, this was your only real choice for asynchronous programming.<\/p>\n<p>Sequential calculations are fairly straightforward in continuation-passing style because you just pass the next step as the continuation.<\/p>\n<pre>\/\/ Synchronous version\r\nauto widget = find_widget(name);\r\nauto success = widget.toggle();\r\nif (!success) report_failure();\r\n\r\n\/\/ Asynchronous version\r\nfind_widget(name).then([=](auto widget) {\r\n    return widget.toggle();\r\n}).then([=](auto success) {\r\n    if (!success) report_failure();\r\n});\r\n<\/pre>\n<p>Iteration is harder to convert to continuation-passing style because you need to restart the task chain, which means you have recursion. A named helper function makes this easier.<\/p>\n<pre>\/\/ Synchronous version\r\nfor (int i = 0; i &lt; 3; i++) {\r\n    widgets[i] = create_widget();\r\n}\r\n\r\n\/\/ Asynchronous version\r\ntask&lt;void&gt; create_many_widgets(Widget* widgets, int count)\r\n{\r\n    if (count == 0) return task_from_result();\r\n\r\n    return create_widget().then([=](auto widget) {\r\n        widgets[0] = widget;\r\n        return create_many_widgets(widgets + 1, count - 1);\r\n    });\r\n}\r\n<\/pre>\n<p>The asynchronous version first creates one widget and saves it into <code>widgets[0]<\/code>. Then it asks to create <code>count - 1<\/code> more widgets starting at <code>widgets + 1<\/code>. This recursively fills in the remaining widgets. The recursion is broken when the count drops to zero.<\/p>\n<p>My joke is that continuation-passing style forces you to write in Scheme. (Note: Not actually a joke.)<\/p>\n<p>Maybe you can write a helper function to automate looping.<\/p>\n<pre>template&lt;typename Callable&gt;\r\ntask&lt;void&gt; do_while_task(Callable&amp;&amp; callable)\r\n{\r\n    using Decayed = std::decay_t&lt;Callable&gt;;\r\n    struct Repeat {\r\n        Repeat(Callable&amp;&amp; callable) :\r\n            f(std::make_shared&lt;Decayed&gt;(\r\n                std::forward&lt;Callable&gt;(callable))) {}\r\n\r\n        std::shared_ptr&lt;Decayed&gt; f;\r\n        task&lt;void&gt; operator()(bool loop) {\r\n            if (loop) {\r\n                return (*f)().then(*this);\r\n            } else {\r\n                return task_from_result();\r\n            }\r\n        }\r\n    };\r\n    Repeat p(std::forward&lt;Callable&gt;(callable));\r\n    return p(true);\r\n}\r\n\r\n\/\/ Sample usage\r\ndo_while_task([i = 0, widgets]() mutable\r\n{\r\n    if (i &gt;= 3) return task_from_result(false);\r\n    return create_widget().then([index = i++, widgets](auto widget)\r\n    {\r\n        widgets[index] = widget;\r\n        return true;\r\n    });\r\n}).then([] {\r\n    printf(\"Done!\\n\");\r\n});\r\n<\/pre>\n<p>The idea here is that you pass <code>do_while_task<\/code> a callable object, and the <code>do_while_task<\/code> invokes the callable, expecting it to return a <code>task&lt;bool&gt;<\/code>. If the returned task completes with <code>true<\/code>, then <code>do_while_task<\/code> invokes the callable again, repeating until the callable&#8217;s returned task finally completes with <code>false<\/code>, at which point we end the task chain by returning a completed task.<\/p>\n<p>The parameter passed to the <code>task::then()<\/code> method is usually a lambda, but it can be any callable. After all, a lambda itself is just a convenient syntax for a callable object. For our usage, we will pass an instance of the <code>Repeat<\/code> class, which is callable thanks to the <code>operator()<\/code> overload.<\/p>\n<p>One of the quirks of the Parallel Patterns Library (PPL) is that the callable passed to <code>then()<\/code> must be copyable.\u00b9 Therefore, we wrap the <code>callable<\/code> inside a <code>std::shared_ptr<\/code> so that the shared pointer can be copied, while still retaining a single copy of the <code>callable<\/code>. This is important because the <code>callable<\/code> passed to <code>do_<wbr \/>while_<wbr \/>task<\/code> might be stateful, and we need to allow it to retain state across calls.<\/p>\n<p>At each iteration, we check whether the previous iteration said to continue running. If not, then we stop. Otherwise, we invoke the callable and request that we (or at least a copy of ourselves) be called back with the result, thus scheduling the next iteration.<\/p>\n<p>The main function starts the festivities by calling the callable with <code>true<\/code>.<\/p>\n<p>We wrote our <code>then()<\/code> handler to accept a <code>bool<\/code>, which means that it is bypassed when an exception occurs. The exception flows off the end of the task chain and is reported to the caller of <code>do_while_task<\/code>.<\/p>\n<p>Next time, we&#8217;ll rewrite this in terms of a more traditional recursion.<\/p>\n<p>\u00b9 If PPL support movable callables, then we could have used a <code>std::<wbr \/>unique_ptr&lt;Callable&gt;<\/code> and done a <code>.then(std::move(*this))<\/code> to move the unique pointer from one iteration to the next.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Keeping track of what to do next.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-108634","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Keeping track of what to do next.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108634","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=108634"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108634\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=108634"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108634"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108634"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}