{"id":95465,"date":"2017-02-17T07:00:00","date_gmt":"2017-02-17T22:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=95465"},"modified":"2019-03-13T01:06:24","modified_gmt":"2019-03-13T08:06:24","slug":"20170217-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20170217-00\/?p=95465","title":{"rendered":"A more efficient solution to the problem of a long-running task running on the thread pool persistent thread"},"content":{"rendered":"<p><a HREF=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20170216-00\/?p=95455\">Last time<\/a>, we found one solution to the problem of the long-running task on the persistent thread: Namely, put the long-running task on a regular thread. But that&#8217;s not the best solution, because it still burns a thread. <\/p>\n<p>The better solution is to let the thread pool manage the wait. Instead of dedicating a task pool thread to waiting around for a specific type of work to do, the thread pool can merge the wait with all the other thread pool wait operations onto a single thread. This keeps all task pool threads available for doing actual work. <\/p>\n<pre>\n\/\/ Error checking elided for expository purposes.\n\nvoid WidgetMonitor::RegisterNotificationWait(\n WidgetNotificationContext* context)\n{\n RegisterWaitForSingleObject(&amp;context-&gt;waitHandle,\n  context-&gt;registryEvent,\n  WidgetNotificationWaitCallback,\n  context,\n  INFINITE,\n  WT_EXECUTEONLYONCE | WT_EXECUTEINPERSISTENTTHREAD);\n RegNotifyChangeKeyValue(context-&gt;hkey, false,\n                         REG_NOTIFY_CHANGE_LAST_SET,\n                         context-&gt;registryEvent, TRUE);\n}\n\nvoid WidgetMonitor::WidgetNotificationStartCallback(void* parameter)\n{\n WidgetNotificationContext* context =\n   reinterpret_cast&lt;WidgetNotificationContext*&gt;(parameter);\n\n context-&gt;hkey = ...;\n context-&gt;registryEvent = ...;\n RegisterNotificationWait(context);\n}\n\nvoid WidgetMonitor::WidgetNotificationWaitCallback(\n    void* parameter, BOOLEAN \/* TimerOrWaitFired *\/)\n{\n WidgetNotificationContext* context =\n   reinterpret_cast&lt;WidgetNotificationContext*&gt;(parameter);\n\n ... process the change ...\n\n RegisterNotificationWait(context);\n}\n\nvoid WidgetMonitor::StartMonitoring()\n{\n auto context = new WidgetNotificationContext();\n QueueUserWorkItem(WidgetNotificationStartCallback,\n                   context,\n                   WT_EXECUTEINPERSISTENTTHREAD);\n}\n\nvoid WidgetMonitor::StopMonitoring(\n    WidgetNotificationContext* context)\n{\n \/\/ WARNING! Massive race conditions here need to be addressed.\n\n if (context-&gt;waitHandle) {\n  UnregisterWait(context-&gt;waitHandle);\n  context-&gt;waitHandle = nullptr;\n }\n ... clean up other resources ...\n delete context;\n}\n<\/pre>\n<p>The basic idea is that you start the ball rolling by queueing <code>Widget&shy;Notification&shy;Start&shy;Callback<\/code> onto the persistent thread. This task opens the registry key and registers the notification. The registration must take place on a persistent thread, and we use the thread pool persistent thread for this purpose, since we are not going to keep a thread captive for the duration of the registration. <\/p>\n<p>When the change occurs, we process it (quickly, since we&#8217;re on the persistent thread), then register another wait. We use one-time waits because we don&#8217;t want two sets of changes to be processed simultaneously. <\/p>\n<p>When the client wants to stop receiving notifications, we unregister the wait, which prevents us from reacting to any future changes. And we clean up any other resources before deleting the context. (Of course, you probably would put all of this code into the <code>Widget&shy;Notification&shy;Context<\/code> destructor, but I&#8217;m putting it here for explicitness.) <\/p>\n<p>Now, this code has race conditions galore. For example, what if a change is being processed at the moment the client deides to stop notifications? I&#8217;ll leave closing all the race windows (and adding proper error handling) as an exercise. You may find that the error handling is a lot easier if you switch to functions like <code>Create&shy;Thread&shy;Pool&shy;Wait<\/code>, which let you preallocate all the resources that will be used by a future wait operation, thereby removing an error scenario. <\/p>\n<p>If processing the change notification is slow (for example, because it waits for the client to respond), then we cannot do that work on the persistent thread. Instead, we should queue the wait callback to a regular thread pool thread, and then queue another <code>Widget&shy;Notification&shy;Start&shy;Callback<\/code> back to the persistent thread to request the next registry notification. While you&#8217;re at it, move all the code that initializes the <code>context<\/code> into the <code>Start&shy;Monitoring<\/code> method. This solves two problems: First, it lets you handle errors more easily, since you can report them to the code that valled <code>Start&shy;Monitoring<\/code>. But more important, it avoids double-initializing the <code>context<\/code>. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Break it up a little more.<\/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-95465","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Break it up a little more.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/95465","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=95465"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/95465\/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=95465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=95465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=95465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}