
{"id":2429,"date":"2017-09-04T00:19:10","date_gmt":"2017-09-04T04:19:10","guid":{"rendered":"http:\/\/www.ikriv.com\/blog\/?p=2429"},"modified":"2017-09-04T00:19:10","modified_gmt":"2017-09-04T04:19:10","slug":"tasktimer-how-to-execute-task-on-a-timer","status":"publish","type":"post","link":"https:\/\/ikriv.com\/blog\/?p=2429","title":{"rendered":"TaskTimer: how to execute task on a timer"},"content":{"rendered":"<p>Over the week-end I created the\u00a0<code>TaskTimer<\/code> class that allows to execute code on timer in an <code>async<\/code> method:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: blue;\">static<\/span>\u00a0<span style=\"color: blue;\">async<\/span>\u00a0<span style=\"color: #2b91af;\">Task<\/span>\u00a0DoStuffOnTimer()\r\n{\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">using<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).Start())\r\n\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">foreach<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0task\u00a0<span style=\"color: blue;\">in<\/span>\u00a0timer)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">await<\/span>\u00a0task;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DoStuff();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>Source code:\u00a0<a href=\"https:\/\/github.com\/ikriv\/tasktimer\">https:\/\/github.com\/ikriv\/tasktimer<\/a><\/p>\n<p>Nuget package:\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/TaskTimer\/1.0.0\">https:\/\/www.nuget.org\/packages\/TaskTimer\/1.0.0<\/a><\/p>\n<p><strong>Motivation<\/strong><\/p>\n<p>To my astonishment, I have found that .NET Task Library does not (seem to) have support for executing a task on timer, similar to <code><a>Observable.Interval()<\/a><\/code>. I needed to execute an activity every second in my <code>async\u00a0<\/code>method,\u00a0and the <a href=\"https:\/\/stackoverflow.com\/questions\/4890915\/is-there-a-task-based-replacement-for-system-threading-timer\">suggestions I found on StackOverflow<\/a> were quite awful: from using <code>Task.Delay()<\/code> to using a dedicated thread and\u00a0<code>Thread.Sleep()<\/code>.<\/p>\n<p><code>Task.Delay()<\/code>\u00a0is good enough in many cases, but it has a problem. Consider this code:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: blue;\">static<\/span>\u00a0<span style=\"color: blue;\">async<\/span>\u00a0<span style=\"color: #2b91af;\">Task<\/span>\u00a0UseTaskDelay()\r\n{\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">while<\/span>\u00a0(<span style=\"color: blue;\">true<\/span>)\r\n\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DoSomething();\u00a0<span style=\"color: green;\">\/\/\u00a0takes\u00a0300ms\u00a0on\u00a0average<\/span>\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">await<\/span>\u00a0<span style=\"color: #2b91af;\">Task<\/span>.Delay(1000);\r\n\u00a0\u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>One execution of the loop takes 1300ms on average, instead of \u00a01000ms we wanted. So, on every execution we will accumulate an additional delay of 300ms. If\u00a0<code>DoSomething()<\/code> takes different time from iteration to iteration, our activity will not only be late, but it will also be irregular. One could, of course, use real-time clock to adjust the delay after every execution, but this is quite laborious and hard to encapsulate.<\/p>\n<p><strong>Using TaskTimer<\/strong><\/p>\n<p>Browser for &#8220;TaskTimer&#8221; package on NuGet and add it to your project. Alternatively, you can get the code from GitHub. The code requires .NET 4.5 or above.<\/p>\n<p>You will need to choose the timer period, and the initial delay, or absolute start time:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: green;\">\/\/\u00a0period=1000ms,\u00a0starts\u00a0immediately<\/span>\r\n<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).Start();\r\n\r\n<span style=\"color: green;\">\/\/\u00a0period=1\u00a0second,\u00a0starts\u00a0immediately<\/span>\r\n<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(<span style=\"color: #2b91af;\">TimeSpan<\/span>.FromSeconds(1)).Start();\r\n\r\n<span style=\"color: green;\">\/\/\u00a0period=1000ms,\u00a0starts\u00a0300ms\u00a0from\u00a0now<\/span>\r\n<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).Start(300);\r\n\r\n<span style=\"color: green;\">\/\/\u00a0period=1000ms,\u00a0starts\u00a05\u00a0minutes\u00a0from\u00a0now<\/span>\r\n<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).Start(<span style=\"color: #2b91af;\">TimeSpan<\/span>.FromMinutes(5));\r\n\r\n<span style=\"color: green;\">\/\/\u00a0period=1000ms,\u00a0starts\u00a0at\u00a0next\u00a0UTC\u00a0midnight<\/span>\r\n<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).StartAt(<span style=\"color: #2b91af;\">DateTime<\/span>.UtcNow.Date.AddDays(1));<\/pre>\n<p>The returned object is a <em>disposable enumberable.<\/em>\u00a0You must call <code>Dispose()<\/code> on it to properly disposer of the underlying timer object. The enumerable iterates over an infinite series of tasks.\u00a0\u00a0Task number N becomes completed after the timer ticks N times. \u00a0The enumerable can be enumerated only once.<\/p>\n<p><strong>Avoiding infinite loop<\/strong><\/p>\n<p>The enumerable returned by the timer is infinite, so you cannot get all the task at once.<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: green;\">\/\/\u00a0won't\u00a0work;\u00a0this\u00a0would\u00a0require\u00a0an\u00a0array\u00a0of\u00a0infinite\u00a0length<\/span>\r\n<span style=\"color: blue;\">var<\/span>\u00a0tasks\u00a0=\u00a0timer.ToArray();<\/pre>\n<p>You can use <code>foreach<\/code> on the timer, but that would be an infinite loop:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: blue;\">using<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).Start())\r\n{\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: green;\">\/\/\u00a0infinite\u00a0loop<\/span>\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">foreach<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0task\u00a0<span style=\"color: blue;\">in<\/span>\u00a0timer)\r\n\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">await<\/span>\u00a0task;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DoSomethingUseful();\r\n\u00a0\u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>If you know the number of iterations in advance, use <code>Take()<\/code> method:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: green;\">\/\/\u00a0loop\u00a0executes\u00a010\u00a0times<\/span>\r\n<span style=\"color: blue;\">foreach<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0task\u00a0<span style=\"color: blue;\">in<\/span>\u00a0timer.Take(10))\r\n{\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">await<\/span>\u00a0task;\r\n\u00a0\u00a0\u00a0\u00a0DoSomethingUseful();\r\n}<\/pre>\n<p>If you don&#8217;t, you can use a cancellation token:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: blue;\">async<\/span>\u00a0<span style=\"color: #2b91af;\">Task<\/span>\u00a0Tick(<span style=\"color: #2b91af;\">CancellationToken<\/span>\u00a0token)\r\n{\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">using<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0timer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimer<\/span>(1000).CancelWith(token).Start())\r\n\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">try<\/span>\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">foreach<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0task\u00a0<span style=\"color: blue;\">in<\/span>\u00a0timer)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">await<\/span>\u00a0task;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DoSomethingUseful();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">catch<\/span>\u00a0(<span style=\"color: #2b91af;\">TaskCanceledException<\/span>)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: #2b91af;\">Console<\/span>.WriteLine(<span style=\"color: #a31515;\">\"Timer\u00a0Canceled\"<\/span>);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p><strong>About Disposable Enumerable<\/strong><\/p>\n<p>I initially implemented the timer sequence as generator method using the <code>yield<\/code> keyword. You can see this in the GitHub history:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color: blue; direction: ltr;\">public<\/span>\u00a0<span style=\"color: #2b91af;\">IEnumerable<\/span>&lt;<span style=\"color: #2b91af;\">Task<\/span>&gt;\u00a0StartOnTimer(...)\r\n{\r\n\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">using<\/span>\u00a0(<span style=\"color: blue;\">var<\/span>\u00a0taskTimer\u00a0=\u00a0<span style=\"color: blue;\">new<\/span>\u00a0<span style=\"color: #2b91af;\">TaskTimerImpl<\/span>(...))\r\n\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">while<\/span>\u00a0(<span style=\"color: blue;\">true<\/span>)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span style=\"color: blue;\">yield<\/span>\u00a0<span style=\"color: blue;\">return<\/span>\u00a0taskTimer.NextTask();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>The trouble with this approach was that calling the <code>Start()<\/code> method did not actually start the timer: the timer would be started only when one begins to <i>iterate<\/i> over it. Furthermore, each iteration will create a new timer. I found this too confusing. I had enough trouble understanding <a href=\"https:\/\/medium.com\/@benlesh\/hot-vs-cold-observables-f8094ed53339\">similar behavior in observables <\/a> to inflict this on the users of my own class.<\/p>\n<p>So, I decided to switch to a schema where the timer gets started immediately upon calling of the <code>Start()<\/code> method. In reactive terms, <code>TaskTimer<\/code> is a hot observable. To stop the timer, one needs to dispose the return value of the <code>Start()<\/code> method, hence the disposable enumerable. To use the timer properly, one should put a <code>foreach<\/code> loop inside a <code>using<\/code> block like in the examples, above.<\/p>\n<p>Also, it is only possible to iterate over the timer once. The following will not work:<\/p>\n<pre style=\"font-family: Consolas; font-size: 13; color: black; background: white;\"><span style=\"color:blue;\">using<\/span>&nbsp;(<span style=\"color:blue;\">var<\/span>&nbsp;timer&nbsp;=&nbsp;<span style=\"color:blue;\">new<\/span>&nbsp;<span style=\"color:#2b91af;\">TaskTimer<\/span>(1000).Start())\r\n{\r\n&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:green;\">\/\/&nbsp;loop&nbsp;executes&nbsp;10&nbsp;times<\/span>\r\n&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:blue;\">foreach<\/span>&nbsp;(<span style=\"color:blue;\">var<\/span>&nbsp;task&nbsp;<span style=\"color:blue;\">in<\/span>&nbsp;timer.Take(10))\r\n&nbsp;&nbsp;&nbsp;&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:blue;\">await<\/span>&nbsp;task;\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DoSomethingUseful();\r\n&nbsp;&nbsp;&nbsp;&nbsp;}\r\n\r\n&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:green;\">\/\/&nbsp;execute&nbsp;20&nbsp;more&nbsp;times&nbsp;--&nbsp;WON&#39;T&nbsp;WORK!&nbsp;Throws&nbsp;InvalidOperationException!<\/span>\r\n&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:blue;\">foreach<\/span>&nbsp;(<span style=\"color:blue;\">var<\/span>&nbsp;task&nbsp;<span style=\"color:blue;\">in<\/span>&nbsp;timer.Take(20))&nbsp;...\r\n}\r\n<\/pre>\n<p>It should be possible to implement a timer with multiple iteration semantics, but I decided it was not worth the complexity.<\/p>\n<p><b>Conclusion<\/b><\/p>\n<p>It&#8217;s a shame Task Library does not provide timer object out of the box. I hope I was able to fill the gap, at least to some extent. In the process I ran into interesting problems with the way <code>IEnumerable<\/code>, <code>IEnumerator<\/code> and <code>IDisposable<\/code> intersect, this is a fascinating world. I hope you do enjoy the result!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Over the week-end I created the\u00a0TaskTimer class that allows to execute code on timer in an async method: static\u00a0async\u00a0Task\u00a0DoStuffOnTimer() { \u00a0\u00a0\u00a0\u00a0using\u00a0(var\u00a0timer\u00a0=\u00a0new\u00a0TaskTimer(1000).Start()) \u00a0\u00a0\u00a0\u00a0{ \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0foreach\u00a0(var\u00a0task\u00a0in\u00a0timer) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await\u00a0task; \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DoStuff(); \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} \u00a0\u00a0\u00a0\u00a0} } Source <a href=\"https:\/\/ikriv.com\/blog\/?p=2429\" class=\"more-link\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"Layout":"","footnotes":""},"categories":[3,4],"tags":[],"class_list":["entry","author-ikriv","post-2429","post","type-post","status-publish","format-standard","category-dotnet","category-hack"],"_links":{"self":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2429","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2429"}],"version-history":[{"count":1,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2429\/revisions"}],"predecessor-version":[{"id":2432,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2429\/revisions\/2432"}],"wp:attachment":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2429"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2429"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2429"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}