{"id":109121,"date":"2023-12-08T07:00:00","date_gmt":"2023-12-08T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109121"},"modified":"2023-12-08T09:30:14","modified_gmt":"2023-12-08T17:30:14","slug":"20231208-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231208-00\/?p=109121","title":{"rendered":"A simpler version of the task sequencer that doesn&#8217;t promise fairness"},"content":{"rendered":"<p>Some time ago, <a title=\"Serializing asynchronous operations in C++\/WinRT\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220915-00\/?p=107182\"> we wrote a <code>task_<wbr \/>sequencer<\/code><\/a> for forcing a series of asynchronous operations to run one after another.<\/p>\n<p>It turned into quite a bit of code, but that was in part because it has to preserve the ordering of tasks, and because it&#8217;s trying to operate purely inside the language with coroutine handles and not rely on external libraries.<\/p>\n<p>But if you&#8217;re willing to build on the C++\/WinRT library and you aren&#8217;t concerned about fairness, then you can do it in an easier way.<\/p>\n<pre>winrt::handle g_serializerHandle{\r\n  winrt::check_pointer(CreateEvent(nullptr,\r\n  \/* manual reset *\/ false, \/* initial state *\/ true,\r\n  nullptr)) };\r\n\r\nstruct SetEvent_scope_exit\r\n{\r\n    SetEvent_scope_exit(void* handle) : handle(handle) {}\r\n    ~SetEvent_scope_exit() {\r\n        SetEvent(handle);\r\n    }\r\n    void* handle;\r\n}\r\n\r\nwinrt::IAsyncAction DoSomethingAsync()\r\n{\r\n    \u27e6 do some stuff \u27e7\r\n\r\n    \/\/ Only one instance of this next block of code can run at a time.\r\n    {\r\n        co_await winrt::resume_on_signal(g_serializerHandle.get());\r\n        auto next = SetEvent_scope_exit(g_serializerHandle.get());\r\n\r\n        \u27e6 do some more stuff, including co_await \u27e7\r\n    }\r\n    \/\/ End of serialization region.\r\n\r\n    \u27e6 do even more stuff \u27e7\r\n}\r\n<\/pre>\n<p>We create an auto-reset event handle called <code>g_serializer\u00adHandle<\/code> and use it to control access to the protected code region. To enter the region, you claim the event handle, and to exit the region, you signal the event handle, which then allows someone else to enter the region. We use a custom RAII type to ensure that the event is signaled even if an exception is thrown.<\/p>\n<p>This is the same as the traditional synchronous code, but we use a kernel object because that allows us to use <code>resume_<wbr \/>on_<wbr \/>signal<\/code> to await asynchronously on the event. This kernel object must be an event (or a semaphore, which is a generalization of an auto-reset event) rather than a Win32 mutex for two reasons.<\/p>\n<p>First, Win32 mutexes have thread affinity, and we can&#8217;t guarantee that all of our asynchronous work occurs on a single thread. Second, Win32 mutexes are reentrant, so it would allow a second attempt to claim the mutex to succeed if the attempt was made on the same thread that claimed the mutex for the work already in progress.<\/p>\n<p>If you are willing to use WIL (the Windows Implementation Library), then you can use the <code>unique_event<\/code> and <code>SetEvent_<wbr \/>scope_<wbr \/>exit<\/code> from WIL.<\/p>\n<pre>\/\/ Default is auto-reset, unsignaled. We change it to signaled.\r\nwil::unique_event g_serializerHandle{ wil::EventOptions::Signaled };\r\n\r\nwinrt::IAsyncAction DoSomethingAsync()\r\n{\r\n    \u27e6 do some stuff \u27e7\r\n\r\n    \/\/ Only one instance of this next block of code can run at a time.\r\n    {\r\n        co_await winrt::resume_on_signal(g_serializerHandle.get());\r\n        auto next = wil::SetEvent_scope_exit(g_serializerHandle.get());\r\n\r\n        \u27e6 do some more stuff, including co_await \u27e7\r\n    }\r\n    \/\/ End of serialization region.\r\n\r\n    \u27e6 do even more stuff \u27e7\r\n}\r\n<\/pre>\n<p>Note that this pattern is unfair: There is no guarantee that the waiting coroutines will be released in the order of arrival. But if fairness is not important, this is a lot less work than the fully-general <code>task_<wbr \/>sequencer<\/code> from last time.<\/p>\n<p><b>Bonus chatter<\/b>: Having to remember to follow up the <code>co_await resume_<wbr \/>on_<wbr \/>signal<\/code> with a <code>SetEvent_<wbr \/>scope_<wbr \/>exit<\/code> is cumbersome. You can remedy that by packing it all into a single method.<\/p>\n<pre>struct async_unfair_mutex\r\n{\r\n    wil::unique_event m_serializerHandle\r\n        { wil::EventOptions::Signaled };\r\n\r\n    wil::task&lt;wil::event_set_scope_exit&gt; LockAsync()\r\n    {\r\n        co_await winrt::resume_on_signal(m_serializerHandle.get());\r\n        co_return wil::SetEvent_scope_exit(m_serializerHandle.get());\r\n    }\r\n};\r\n<\/pre>\n<p>We can&#8217;t use <code>winrt::<wbr \/>IAsyncOperation&lt;wil::<wbr \/>event_<wbr \/>set_<wbr \/>scope_<wbr \/>exit&gt;<\/code> because <code>IAsyncOperation<\/code> works only with Windows Runtime types. We also can&#8217;t use <code>concurrency::<wbr \/>task&lt;<wbr \/>wil::<wbr \/>event_<wbr \/>set_<wbr \/>scope_<wbr \/>exit&gt;<\/code> because PPL&#8217;s <code>task<\/code> requires that the task return type be default-constructible (okay) and copyable (nope).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just let a kernel object control the access.<\/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-109121","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Just let a kernel object control the access.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109121","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=109121"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109121\/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=109121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}