{"id":5953,"date":"2012-11-29T07:00:00","date_gmt":"2012-11-29T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/11\/29\/various-ways-of-performing-an-operation-asynchronously-after-a-delay\/"},"modified":"2012-11-29T07:00:00","modified_gmt":"2012-11-29T07:00:00","slug":"various-ways-of-performing-an-operation-asynchronously-after-a-delay","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20121129-00\/?p=5953","title":{"rendered":"Various ways of performing an operation asynchronously after a delay"},"content":{"rendered":"<p>\nOkay, if you have a UI thread that pumps messages, then the easiest\nway to perform an operation after a delay is to set a timer.\nBut let&#8217;s say you don&#8217;t have a UI thread that you can count on.\n<\/p>\n<p>\nOne method is to burn a thread:\n<\/p>\n<pre>\n#define ACTIONDELAY (30 * 60 * 1000) \/\/ 30 minutes, say\nDWORD CALLBACK ActionAfterDelayProc(void *)\n{\n Sleep(ACTIONDELAY);\n Action();\n return 0;\n}\nBOOL PerformActionAfterDelay()\n{\n DWORD dwThreadId;\n HANDLE hThread = CreateThread(NULL, 0, ActionAfterDelayProc,\n                               NULL, 0, &amp;dwThreadId);\n BOOL fSuccess = hThread != NULL;\n if (hThread) {\n  CloseHandle(hThread);\n }\n return fSuccess;\n}\n<\/pre>\n<p>\nLess expensive is to borrow a thread from the thread pool:\n<\/p>\n<pre>\nBOOL PerformActionAfterDelay()\n{\n return QueueUserWorkItem(ActionAfterDelayProc, NULL,\n                          <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/07\/22\/441785.aspx\">WT_EXECUTELONGFUNCTION<\/a>);\n}\n<\/pre>\n<p>\nBut both of these methods hold a thread hostage for the\nduration of the delay.\nBetter would be to consume a thread only when the action is\nin progress.\nFor that, you can use a thread pool timer:\n<\/p>\n<pre>\nvoid CALLBACK ActionAfterDelayProc(void *lpParameter, BOOLEAN)\n{\n HANDLE *phTimer = static_cast&lt;HANDLE *&gt;(lpParameter);\n Action();\n DeleteTimerQueueTimer(NULL, *phTimer, NULL);\n delete phTimer;\n}\nBOOL PerformActionAfterDelay()\n{\n BOOL fSuccess = FALSE;\n HANDLE *phTimer = new(std::nothrow) HANDLE;\n if (phTimer != NULL) {\n  if (CreateTimerQueueTimer(\n     phTimer, NULL, ActionAfterDelayProc, phTimer,\n     ACTIONDELAY, 0, WT_EXECUTEONLYONCE)) {\n   fSuccess = TRUE;\n  }\n }\n if (!fSuccess) {\n  delete phTimer;\n }\n return fSuccess;\n}\n<\/pre>\n<p>\nThe timer queue timer technique is complicated by the\nfact that we want the timer to self-cancel, so it needs\nto know its handle, but we don&#8217;t know the handle until\nafter we&#8217;ve scheduled it, at which point it&#8217;s too late\nto pass the handle as a parameter.\nIn other words, we&#8217;d ideally like to create the timer,\nand then once we get the handle, go back in time and\npass the handle as the parameter to\n<code>Create&shy;Timer&shy;Queue&shy;Timer<\/code>.\nSince the Microsoft Research people haven&#8217;t yet\nperfected their time machine, we solve this problem\nby passing the handle by address:\nThe\n<code>Create&shy;Timer&shy;Queue&shy;Timer<\/code>\nfunction fills the address with the timer,\nso that the callback function can read it back out.\n<\/p>\n<p>\nIn practice, this additional work is no additional work at all,\nbecause you&#8217;re already passing some data to the callback\nfunction, probably an object or at least a pointer to a structure.\nYou can stash the timer handle inside that object.\nIn our case, our object is just the handle itself.\nIf you prefer to be more explicit:\n<\/p>\n<pre>\nstruct ACTIONINFO\n{\n HANDLE hTimer;\n};\nvoid CALLBACK ActionAfterDelayProc(void *lpParameter, BOOLEAN)\n{\n ACTIONINFO *pinfo = static_cast&lt;ACTIONINFO *&gt;(lpParameter);\n Action();\n DeleteTimerQueueTimer(NULL, pinfo-&gt;hTimer, NULL);\n delete pinfo;\n}\nBOOL PerformActionAfterDelay()\n{\n BOOL fSuccess = FALSE;\n ACTIONINFO *pinfo = new(std::nothrow) ACTIONINFO;\n if (pinfo != NULL) {\n  if (CreateTimerQueueTimer(\n     &amp;pinfo-&gt;hTimer, NULL, ActionAfterDelayProc, pinfo,\n     ACTIONDELAY, 0, WT_EXECUTEONLYONCE)) {\n   fSuccess = TRUE;\n  }\n }\n if (!fSuccess) {\n  delete pinfo;\n }\n return fSuccess;\n}\n<\/pre>\n<p>\nThe threadpool functions were redesigned in Windows Vista\nto allow for greater reliability and predictability.\nFor example, the operations of creating a timer and setting it\ninto action are separated so that you can preallocate your\ntimer objects (inactive) at a convenient time.\nSetting the timer itself cannot fail (assuming valid parameters).\nThis makes it easier to handle error conditions since all the\nerrors happen when you preallocate the timers,\nand you can deal with the problem up front,\nrather than proceeding ahead for a while\nand then realizing,\n&#8220;Oops, I wanted to set that timer but I couldn&#8217;t.\nNow how do I report the error and unwind all the work that I&#8217;ve done so far?&#8221;\n(There are other new features, like <i>cleanup groups<\/i> that let\nyou clean up multiple objects with a single call,\nand being able to associate an execution environment with a library,\nso that the DLL is not unloaded while it still has active thread pool\nobjects.)\n<\/p>\n<p>\nThe result is, however, a bit more typing, since there are now two steps,\ncreating and setting.\nOn the other hand,\nthe new threadpool callback is explicitly passed the\n<code>PTP_TIMER<\/code>, so we don&#8217;t have to play any\nweird time-travel games to get the handle to the callback,\nlike we did with\n<code>Create&shy;Timer&shy;Queue&shy;Timer<\/code>.\n<\/p>\n<pre>\nvoid CALLBACK ActionAfterDelayProc(\n    PTP_CALLBACK_INSTANCE, PVOID, PTP_TIMER Timer)\n{\n Action();\n CloseThreadpoolTimer(Timer);\n}\nBOOL PerformActionAfterDelay()\n{\n BOOL fSuccess = FALSE;\n PTP_TIMER Timer = CreateThreadpoolTimer(\n                      ActionAfterDelayProc, NULL, NULL);\n if (Timer) {\n  LONGLONG llDelay = -ACTIONDELAY * 10000LL;\n  FILETIME ftDueTime = { (DWORD)llDelay, (DWORD)(llDelay &gt;&gt; 32) };\n  SetThreadpoolTimer(Timer, &amp;ftDueTime, 0, 0); \/\/ never fails!\n  fSuccess = TRUE;\n }\n return fSuccess;\n}\n<\/pre>\n<p>\nAnyway, that&#8217;s a bit of a whirlwind tour of some of the ways\nof arranging for code to run after a delay.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Okay, if you have a UI thread that pumps messages, then the easiest way to perform an operation after a delay is to set a timer. But let&#8217;s say you don&#8217;t have a UI thread that you can count on. One method is to burn a thread: #define ACTIONDELAY (30 * 60 * 1000) \/\/ [&hellip;]<\/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-5953","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Okay, if you have a UI thread that pumps messages, then the easiest way to perform an operation after a delay is to set a timer. But let&#8217;s say you don&#8217;t have a UI thread that you can count on. One method is to burn a thread: #define ACTIONDELAY (30 * 60 * 1000) \/\/ [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/5953","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=5953"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/5953\/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=5953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=5953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=5953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}