{"id":99395,"date":"2018-08-02T07:00:00","date_gmt":"2018-08-02T21:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=99395"},"modified":"2024-06-18T09:51:20","modified_gmt":"2024-06-18T16:51:20","slug":"20180802-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20180802-00\/?p=99395","title":{"rendered":"Creating an awaitable lock for C++ PPL tasks"},"content":{"rendered":"<p>The C# language (well, more accurately, the BCL) has <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.threading.semaphoreslim(v=vs.110).aspx\"> the <code>Reader\u00adWriter\u00adLock\u00adSlim<\/code> class<\/a> which has <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.threading.semaphoreslim(v=vs.110).aspx\"> a <code>Wait\u00adAsync<\/code> method<\/a> which returns a task that completes asynchronously when the lock has been acquired. I needed an equivalent for the Parallel Patterns Library (PPL), and since I couldn&#8217;t find one, I ended up writing one. (If you can find one, please let me know!)<\/p>\n<pre>\/\/ AsyncUILock is a nonrecursive lock that can be waited on\r\n\/\/ asynchronously from a UI thread.\r\nclass AsyncUILock\r\n{\r\npublic:\r\n  Concurrency::task&lt;void&gt; WaitAsync()\r\n  {\r\n    std::lock_guard&lt;std::mutex&gt; guard(mutex);\r\n    if (!locked) {\r\n      \/\/ Lock is available. Acquire it.\r\n      locked = true;\r\n      return <a href=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20180801-00\/?p=99385\">completed_apartment_aware_task<\/a>();\r\n    }\r\n\r\n    \/\/ Lock is not available.\r\n    return completed_apartment_aware_task()\r\n      .then([captured_completion = completion] {\r\n      \/\/ Wait for it to become available.\r\n      return Concurrency::create_task(captured_completion);\r\n    }).then([this] {\r\n      \/\/ Then try again.\r\n      return WaitAsync();\r\n    });\r\n  }\r\n\r\n\/\/ <span style=\"text-decoration: line-through;\">void Release()<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">  std::lock_guard&lt;std::mutex&gt; guard(mutex);<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">  locked = false;<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">  auto previousCompletion = completion;<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">  completion = Concurrency::task_completion_event&lt;void&gt;();<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">  previousCompletion.set();<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n\r\n  void Release()\r\n  {\r\n    [&amp;] {\r\n      \/\/ See follow-up article\r\n      std::lock_guard&lt;std::mutex&gt; guard(mutex);\r\n      locked = false;\r\n      return std::exchange(completion, {});\r\n    }().set();\r\n  }\r\n\r\nprivate:\r\n  std::mutex mutex;\r\n  bool locked = false;\r\n  Concurrency::task_completion_event&lt;void&gt; completion;\r\n};\r\n<\/pre>\n<p>The object consists of a <code>std::mutex<\/code> which protects the internal state, a flag that indicates whether the object has been claimed, and a task completion event that we use to signal anybody waiting on the lock that they should check again.<\/p>\n<p><b>Update<\/b>: We signal the completion event after dropping the lock.<\/p>\n<p>I could have used an <code>SRWLock<\/code> instead of a <code>std::mutex<\/code>, but I was lazy and wanted to take advantage of the existing <code>std::lock_guard<\/code>.<\/p>\n<p>You can perform async waits on this object in the usual manner. For example:<\/p>\n<pre>AsyncUILock lock;\r\n\r\nvoid DoSomething()\r\n{\r\n  lock.WaitAsync().then([]{\r\n    \/\/ do something with the lock held.\r\n    lock.Release();\r\n  });\r\n}\r\n<\/pre>\n<p>or if you prefer <code>co_await<\/code> (and you probably do):<\/p>\n<pre>AsyncUILock lock;\r\n\r\nvoid DoSomething()\r\n{\r\n  co_await lock.WaitAsync();\r\n  \/\/ do something with the lock held.\r\n  lock.Release();\r\n}\r\n<\/pre>\n<p>At this point, you might decide to return an RAII type to ensure that the lock doesn&#8217;t leak. I&#8217;ll leave that as an exercise.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Rolling our own.<\/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-99395","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Rolling our own.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/99395","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=99395"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/99395\/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=99395"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=99395"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=99395"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}