{"id":2791,"date":"2015-01-14T13:00:09","date_gmt":"2015-01-14T13:00:09","guid":{"rendered":"http:\/\/writeasync.net\/?p=2791"},"modified":"2015-01-10T17:16:20","modified_gmt":"2015-01-10T17:16:20","slug":"eventhandler-and-asynchrony","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=2791","title":{"rendered":"EventHandler and asynchrony"},"content":{"rendered":"<p><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/edzehd2t(v=vs.110).aspx\">Events in .NET<\/a> are for the most part inherently synchronous <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms173175.aspx\">multicast delegates<\/a>. It is therefore no surprise that <a href=\"http:\/\/stackoverflow.com\/questions\/12451609\/how-to-await-raising-an-eventhandler-event\"><code>async<\/code> and traditional <code>EventHandler<\/code>-based delegates do not mix<\/a>. Today I&#8217;ll illustrate one design approach which I&#8217;ve used successfully to get around this incompatibility.<\/p>\n<p>The scenario (slightly simplified for explanatory purposes) was a looping scheduler of background work items. The scheduler had various lifecycle events such as <code>Draining<\/code> (&#8220;waiting for pending work items to complete&#8221;), <code>Paused<\/code> (&#8220;all further work is on hold for now&#8221;), <code>Resuming<\/code> (&#8220;preparing to start work items again&#8221;), and so on. This scheduler was built to be purely async and as such its inner loop expected to interact only with async-friendly (i.e. non-blocking) code. This of course presented a challenge since the lifecycle events were implemented as good old <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/db0etb8x.aspx\"><code>EventHandler&lt;TEventArgs&gt;<\/code><\/a> derivatives.<\/p>\n<p>One way to &#8220;solve&#8221; this is by restriction &#8212; i.e. no async code allowed for event subscribers. I don&#8217;t mean to denigrate this approach because this is actually a very reasonable strategy which could simplify the design. However, in the scenario I&#8217;m describing there were at least a few valid use cases which could be concisely implemented by making some calls to a remote server. There surely would have been viable workarounds, perhaps by implementing such logic as a work item itself to be scheduled. But this would have been less straightforward and possibly encourage users of the library to take the relatively easier (but arguably wrong) path of just writing blocking sync code directly in the handler.<\/p>\n<p>Instead, I got some inspiration from <code>CancelEventArgs<\/code> and the way it allows the user to <em>set<\/em> the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.componentmodel.canceleventargs.cancel(v=vs.110).aspx\"><code>Cancel<\/code> property<\/a> to relay information <em>back<\/em> to the event sender. With that in mind, I created this base class <code>AsyncEventArgs<\/code> to maintain a list of async tasks to be scheduled after all handlers were done:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class AsyncEventArgs : EventArgs\r\n{\r\n    public AsyncEventArgs()\r\n    {\r\n        this.Tasks = new List&lt;Func&lt;Task&gt;&gt;();\r\n    }\r\n\r\n    public IList&lt;Func&lt;Task&gt;&gt; Tasks { get; private set; }\r\n}\r\n<\/pre>\n<p>Here is a sample use case which invokes a <a href=\"http:\/\/worldclockapi.com\/\">world clock REST API<\/a> to decide whether to stop:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nDateTime initialTime = await worldClock.GetTimeAsync();\r\nLog(&quot;Time is &quot; + TimeString(initialTime));\r\n\r\n\/\/ An infinite loop of successive 100 ms delay tasks\r\nFunc&lt;Task&gt; delayAsync = delegate\r\n{\r\n    Log(&quot;Delaying...&quot;);\r\n    return Task.Delay(TimeSpan.FromSeconds(0.1d));\r\n};\r\n\r\nLoopingScheduler scheduler = new LoopingScheduler(delayAsync);\r\n\r\nFunc&lt;DateTime, TimeSpan, Task&gt; throwIfMaxDurationAsync = async delegate(DateTime start, TimeSpan duration)\r\n{\r\n    Log(&quot;Checking time...&quot;);\r\n    DateTime now = await worldClock.GetTimeAsync();\r\n    Log(&quot;Time is &quot; + TimeString(now));\r\n    if ((now - start) &gt; duration)\r\n    {\r\n        throw new InvalidOperationException(&quot;Max duration reached!&quot;);\r\n    }\r\n};\r\n\r\n\/\/ On every pause cycle, check if duration is reached based on world time\r\nscheduler.Paused += delegate(object sender, AsyncEventArgs e)\r\n{\r\n    Log(&quot;Paused.&quot;);\r\n    e.Tasks.Add(() =&gt; throwIfMaxDurationAsync(initialTime, TimeSpan.FromSeconds(2.0d)));\r\n};\r\n\r\nTimeSpan pauseInterval = TimeSpan.FromSeconds(0.5d);\r\nawait scheduler.RunAsync(pauseInterval);\r\n<\/pre>\n<p>The code is available for your perusal on GitHub: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync\/tree\/master\/projects\/EventHandlerSample\">EventHandlerSample<\/a>. Read, use, extend, or modify at will.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Events in .NET are for the most part inherently synchronous multicast delegates. It is therefore no surprise that async and traditional EventHandler-based delegates do not mix. Today I&#8217;ll illustrate one design approach which I&#8217;ve used successfully to get around this&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,91],"tags":[],"class_list":["post-2791","post","type-post","status-publish","format-standard","hentry","category-async","category-design"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/2791","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2791"}],"version-history":[{"count":0,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/2791\/revisions"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2791"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2791"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2791"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}