{"id":4061,"date":"2015-07-01T13:00:34","date_gmt":"2015-07-01T13:00:34","guid":{"rendered":"http:\/\/writeasync.net\/?p=4061"},"modified":"2015-07-01T04:17:01","modified_gmt":"2015-07-01T04:17:01","slug":"the-battle-of-async-methods","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=4061","title":{"rendered":"The battle of async methods"},"content":{"rendered":"<p>In a contest between async proper, <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/hh195051(v=vs.110).aspx\">Task.Run<\/a>, and dedicated threads, who will win? Let&#8217;s gather some data points for a specific scenario and find out!<\/p>\n<p>Today&#8217;s benchmark will make use of a trivial single process <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.servicemodel.netnamedpipebinding(v=vs.110).aspx\">WCF named pipes<\/a> application using .NET 4.5. The complete source code is as follows:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nnamespace AsyncPerf\r\n{\r\n    using System;\r\n    using System.Collections.Generic;\r\n    using System.Reflection;\r\n    using System.ServiceModel;\r\n    using System.ServiceModel.Description;\r\n    using System.Threading;\r\n    using System.Threading.Tasks;\r\n\r\n    internal sealed class Program\r\n    {\r\n        private static readonly Uri BaseAddress = new Uri(&quot;net.pipe:\/\/localhost\/TestService&quot;);\r\n\r\n        private static void Main(string&#x5B;] args)\r\n        {\r\n            if (args.Length != 4)\r\n            {\r\n                Console.WriteLine(&quot;Provide the task count, message count, operation delay (ms), and method name.&quot;);\r\n                return;\r\n            }\r\n\r\n            int senderCount = int.Parse(args&#x5B;0]);\r\n            int messageCount = int.Parse(args&#x5B;1]);\r\n            TimeSpan delay = TimeSpan.FromMilliseconds(int.Parse(args&#x5B;2]));\r\n            MethodInfo method = typeof(AsyncMethods).GetMethod(args&#x5B;3]);\r\n            Func&lt;int, Task&gt; func = (Func&lt;int, Task&gt;)method.CreateDelegate(typeof(Func&lt;int, Task&gt;));\r\n\r\n            ServiceHost host = new ServiceHost(new TestService(delay), BaseAddress);\r\n            ServiceThrottlingBehavior throttlingBehavior = new ServiceThrottlingBehavior\r\n            {\r\n                MaxConcurrentCalls = int.MaxValue,\r\n                MaxConcurrentInstances = int.MaxValue,\r\n                MaxConcurrentSessions = int.MaxValue,\r\n            };\r\n            host.Description.Behaviors.Add(throttlingBehavior);\r\n            host.AddServiceEndpoint(typeof(ITestService), GetBinding(), string.Empty);\r\n            host.Open();\r\n\r\n            List&lt;Task&gt; tasks = new List&lt;Task&gt;();\r\n            for (int i = 0; i &lt; senderCount; ++i)\r\n            {\r\n                tasks.Add(func(messageCount));\r\n            }\r\n\r\n            Task.WaitAll(tasks.ToArray());\r\n        }\r\n\r\n        private static NetNamedPipeBinding GetBinding()\r\n        {\r\n            return new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);\r\n        }\r\n\r\n        &#x5B;ServiceContract(Name = &quot;ITestService&quot;)]\r\n        private interface ITestServiceLegacyAsync\r\n        {\r\n            &#x5B;OperationContract(AsyncPattern = true)]\r\n            IAsyncResult BeginIncrement(int input, AsyncCallback callback, object state);\r\n\r\n            int EndIncrement(IAsyncResult result);\r\n        }\r\n\r\n        &#x5B;ServiceContract(Name = &quot;ITestService&quot;)]\r\n        private interface ITestServiceTaskAsync\r\n        {\r\n            &#x5B;OperationContract(AsyncPattern = true)]\r\n            Task&lt;int&gt; IncrementAsync(int input);\r\n        }\r\n\r\n        &#x5B;ServiceContract]\r\n        private interface ITestService\r\n        {\r\n            &#x5B;OperationContract]\r\n            int Increment(int input);\r\n        }\r\n\r\n        &#x5B;ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]\r\n        private sealed class TestService : ITestService\r\n        {\r\n            private readonly TimeSpan delay;\r\n\r\n            public TestService(TimeSpan delay)\r\n            {\r\n                this.delay = delay;\r\n            }\r\n\r\n            public int Increment(int input)\r\n            {\r\n                Thread.Sleep(delay);\r\n                return input + 1;\r\n            }\r\n        }\r\n\r\n        private static class AsyncMethods\r\n        {\r\n            public static async Task SendTaskRunAsync(int count)\r\n            {\r\n                ITestService proxy = ChannelFactory&lt;ITestService&gt;.CreateChannel(GetBinding(), new EndpointAddress(BaseAddress));\r\n                int i = 0;\r\n                while (i &lt; count)\r\n                {\r\n                    i = await Task.Run(() =&gt; proxy.Increment(i));\r\n                }\r\n            }\r\n\r\n            public static Task SendThreadAsync(int count)\r\n            {\r\n                Action action = delegate\r\n                {\r\n                    ITestService proxy = ChannelFactory&lt;ITestService&gt;.CreateChannel(GetBinding(), new EndpointAddress(BaseAddress));\r\n                    int i = 0;\r\n                    while (i &lt; count)\r\n                    {\r\n                        i = proxy.Increment(i);\r\n                    }\r\n                };\r\n\r\n                return Task.Factory.StartNew(action, TaskCreationOptions.LongRunning);\r\n            }\r\n\r\n            public static async Task SendLegacyTask2Async(int count)\r\n            {\r\n                ITestServiceLegacyAsync proxy = ChannelFactory&lt;ITestServiceLegacyAsync&gt;.CreateChannel(GetBinding(), new EndpointAddress(BaseAddress));\r\n                int i = 0;\r\n                while (i &lt; count)\r\n                {\r\n                    i = await Task.Factory.FromAsync(\r\n                        (a, c, s) =&gt; ((ITestServiceLegacyAsync)s).BeginIncrement(a, c, s),\r\n                        r =&gt; ((ITestServiceLegacyAsync)r.AsyncState).EndIncrement(r),\r\n                        i,\r\n                        proxy);\r\n                }\r\n            }\r\n\r\n            public static async Task SendLegacyTaskAsync(int count)\r\n            {\r\n                ITestServiceLegacyAsync proxy = ChannelFactory&lt;ITestServiceLegacyAsync&gt;.CreateChannel(GetBinding(), new EndpointAddress(BaseAddress));\r\n                int i = 0;\r\n                while (i &lt; count)\r\n                {\r\n                    i = await Task.Factory.FromAsync(\r\n                        (c, s) =&gt; proxy.BeginIncrement(i, c, s),\r\n                        r =&gt; proxy.EndIncrement(r),\r\n                        null);\r\n                }\r\n            }\r\n\r\n            public static async Task SendRealTaskAsync(int count)\r\n            {\r\n                ITestServiceTaskAsync proxy = ChannelFactory&lt;ITestServiceTaskAsync&gt;.CreateChannel(GetBinding(), new EndpointAddress(BaseAddress));\r\n                int i = 0;\r\n                while (i &lt; count)\r\n                {\r\n                    i = await proxy.IncrementAsync(i);\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>(Note the <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms735114(v=vs.110).aspx\">service throttling settings<\/a>, which will prevent us from hitting any artificial concurrency limits.)<\/p>\n<p>We have five different algorithms in the runnings:<\/p>\n<ol>\n<li>SendRealTaskAsync: Uses a Task-based async contract to send messages.<\/li>\n<li>SendLegacyTaskAsync: Uses a legacy asynchronous programming model contract wrapped on the outside by <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dd321335(v=vs.110).aspx\">Task.Factory.FromAsync<\/a>.<\/li>\n<li>SendLegacyTask2Async: Same as SendLegacyTaskAsync except using a different overload of <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dd321448(v=vs.110).aspx\">Task.Factory.FromAsync<\/a> which avoids the use of a generated closure.<\/li>\n<li>SendThreadAsync: Uses a synchronous contract and offloads the entire send loop to a new thread.<\/li>\n<li>SendTaskRunAsync: Uses a synchronous contract wrapped on the outside with Task.Run.<\/li>\n<\/ol>\n<p>All measurements were taken on a <a href=\"https:\/\/www.microsoft.com\/surface\/en-us\/products\/surface-pro-3\">Surface Pro 3<\/a> i7 model (quad core, 1.7-3.3 GHz, 8 GB RAM).<\/p>\n<h2>Contest 1: 1000 concurrent senders, no server-side delay<\/h2>\n<p>In the first contest, we will compare performance without any server delay at all, i.e. the message responses arrive as fast as possible.<\/p>\n<div id=\"attachment_4091\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTask2Async_1K.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4091\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTask2Async_1K-300x227.gif\" alt=\"SendLegacyTask2Async\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4091\" \/><\/a><p id=\"caption-attachment-4091\" class=\"wp-caption-text\">SendLegacyTask2Async<\/p><\/div>\n<div id=\"attachment_4101\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTaskAsync_1K.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4101\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTaskAsync_1K-300x227.gif\" alt=\"SendLegacyTaskAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4101\" \/><\/a><p id=\"caption-attachment-4101\" class=\"wp-caption-text\">SendLegacyTaskAsync<\/p><\/div>\n<div id=\"attachment_4111\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendRealTaskAsync_1K.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4111\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendRealTaskAsync_1K-300x227.gif\" alt=\"SendRealTaskAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4111\" \/><\/a><p id=\"caption-attachment-4111\" class=\"wp-caption-text\">SendRealTaskAsync<\/p><\/div>\n<div id=\"attachment_4121\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendTaskRunAsync_1K.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4121\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendTaskRunAsync_1K-300x227.gif\" alt=\"SendTaskRunAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4121\" \/><\/a><p id=\"caption-attachment-4121\" class=\"wp-caption-text\">SendTaskRunAsync<\/p><\/div>\n<div id=\"attachment_4131\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendThreadAsync_1K.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4131\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendThreadAsync_1K-300x227.gif\" alt=\"SendThreadAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4131\" \/><\/a><p id=\"caption-attachment-4131\" class=\"wp-caption-text\">SendThreadAsync<\/p><\/div>\n<p>Clearly, dedicated threads are bad for such a high degree of concurrency. But what is this? Task.Run seems to outperform the genuine Task-based async contract. While this may at first be a shock, consider the fact that there is no server delay. Hence, the completion time of an operation here is going to be nearly as fast as a local method call. The amount of thread switching and async overhead does not pay off in this case.<\/p>\n<h2>Contest 2: 1000 concurrent senders, 100 ms server-side delay<\/h2>\n<p>In the next contest, we introduce 100 ms of delay on the message responses.<\/p>\n<div id=\"attachment_4191\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTask2Async_1K_100ms.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4191\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTask2Async_1K_100ms-300x227.gif\" alt=\"SendLegacyTask2Async\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4191\" \/><\/a><p id=\"caption-attachment-4191\" class=\"wp-caption-text\">SendLegacyTask2Async<\/p><\/div>\n<div id=\"attachment_4201\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTaskAsync_1K_100ms.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4201\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendLegacyTaskAsync_1K_100ms-300x227.gif\" alt=\"SendLegacyTaskAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4201\" \/><\/a><p id=\"caption-attachment-4201\" class=\"wp-caption-text\">SendLegacyTaskAsync<\/p><\/div>\n<div id=\"attachment_4211\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendRealTaskAsync_1K_100ms.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4211\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendRealTaskAsync_1K_100ms-300x227.gif\" alt=\"SendRealTaskAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4211\" \/><\/a><p id=\"caption-attachment-4211\" class=\"wp-caption-text\">SendRealTaskAsync<\/p><\/div>\n<div id=\"attachment_4221\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendTaskRunAsync_1K_100ms.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4221\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendTaskRunAsync_1K_100ms-300x227.gif\" alt=\"SendTaskRunAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4221\" \/><\/a><p id=\"caption-attachment-4221\" class=\"wp-caption-text\">SendTaskRunAsync<\/p><\/div>\n<div id=\"attachment_4231\" style=\"width: 310px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendThreadAsync_1K_100ms.gif\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-4231\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2015\/07\/SendThreadAsync_1K_100ms-300x227.gif\" alt=\"SendThreadAsync\" width=\"300\" height=\"227\" class=\"size-medium wp-image-4231\" \/><\/a><p id=\"caption-attachment-4231\" class=\"wp-caption-text\">SendThreadAsync<\/p><\/div>\n<p>Dedicated threads lose, as expected. We didn&#8217;t even get to the point where all threads were even active given the various delays in creation of new thread pool threads and the total thread count limit. Real task-based async is on par with SendLegacyTask2Async and a bit better than SendLegacyTaskAsync as expected. For a more proper comparison, it might have been prudent to also implement server-side async (this is partially why the threads seem to be growing to such a high amount) &#8212; but the general trend is relatively clear nonetheless.<\/p>\n<p>Conclusion? When the operations being performed are basically CPU-bound, the overhead of async might give you pause. However, in a more realistic situation where most I\/O time is spent waiting around, tasks do the trick quite well.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a contest between async proper, Task.Run, and dedicated threads, who will win? Let&#8217;s gather some data points for a specific scenario and find out! Today&#8217;s benchmark will make use of a trivial single process WCF named pipes application using&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,61,71],"tags":[],"class_list":["post-4061","post","type-post","status-publish","format-standard","hentry","category-async","category-concurrency","category-diagnostics"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/4061","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=4061"}],"version-history":[{"count":0,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/4061\/revisions"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4061"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4061"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4061"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}