{"id":5392,"date":"2018-02-04T13:00:16","date_gmt":"2018-02-04T13:00:16","guid":{"rendered":"http:\/\/writeasync.net\/?p=5392"},"modified":"2018-01-21T22:33:28","modified_gmt":"2018-01-21T22:33:28","slug":"async-holes-stringcontent","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5392","title":{"rendered":"Async holes: StringContent"},"content":{"rendered":"<p><a href=\"http:\/\/writeasync.net\/?p=5388\">In a previous post<\/a>, I introduced the concept of &#8220;async holes&#8221; &#8212; those unexpected gaps and obstacles when using asynchronous APIs in .NET. This time I will tell the tale of the async hole in <code>StringContent<\/code>.<\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.io.compression.ziparchive?view=netframework-4.7.1\">System.Net.Http.StringContent<\/a> is the derived class of choice for testing code written using <a href=\"https:\/\/www.asp.net\/web-api\">ASP.NET Web API<\/a>. It is derived from <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.net.http.bytearraycontent?view=netframework-4.7.1\">ByteArrayContent<\/a> and in turn from <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.net.http.httpcontent?view=netframework-4.7.1\">HttpContent<\/a>. Here is a simple client-side use case for <code>HttpContent<\/code>, demonstrating how to stream content from an HTTP REST response:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class MyClient\r\n{\r\n    private readonly HttpClient client;\r\n\r\n    public MyClient(HttpClient client)\r\n    {\r\n        this.client = client;\r\n    }\r\n\r\n    public async Task DownloadAsync(string name, Stream output)\r\n    {\r\n        using (HttpResponse response = await this.client.GetAsync(name))\r\n        {\r\n            HttpContent content = response.Content;\r\n            await content.CopyToAsync(output);\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>In cases where the content may be quite large (say, a huge file download), you could provide a <code>FileStream<\/code> to avoid needing to buffer the entire response in memory at once. We also have the flexibility of providing a <code>MemoryStream<\/code> when we know that we want the data to end up only in memory &#8212; the most common case in a unit test. In that situation, you would likely also provide the content from a fixed memory buffer and avoid any network I\/O; for that purpose, we have <code>StringContent<\/code> and\/or <code>ByteArrayContent<\/code>. Now this is where the async hole comes in. Let&#8217;s use this code snippet to demonstrate the problem:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate static async Task ReadContentAsync()\r\n{\r\n    Encoding encoding = Encoding.UTF8;\r\n    string text = &quot;hello!&quot;;\r\n\r\n    HttpContent content = new StringContent(text, encoding);\r\n\r\n    Log(&quot;Copying to stream...&quot;);\r\n    using (MemoryStream stream = new MemoryStream())\r\n    {\r\n        await content.CopyToAsync(stream);\r\n\r\n        string result = encoding.GetString(stream.ToArray());\r\n        Log(&quot;Result: '{0}'&quot;, result);\r\n    }\r\n}\r\n\r\nprivate static void Log(string format, params object&#x5B;] args)\r\n{\r\n    string text = string.Format(CultureInfo.InvariantCulture, format, args);\r\n    Console.WriteLine(&quot;&#x5B;{0}] {1}&quot;, Thread.CurrentThread.ManagedThreadId, text);\r\n}\r\n<\/pre>\n<p>In this example, you will note that all pieces of data are 100% in-memory. Hence, you might expect that the stream copy would follow the pattern of most <code>MemoryStream<\/code> use cases and complete 100% synchronously. Sadly, that is not the case, according to the output:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;1] Copying to stream...\r\n&#x5B;4] Result: 'hello!'\r\n<\/pre>\n<p>The stream copy has a pesky thread switch! To see why, let&#8217;s fire up <a href=\"https:\/\/github.com\/icsharpcode\/ILSpy\/releases\">ILSpy<\/a> and &#8220;Open from GAC&#8230;&#8221; the <code>System.Net.Http<\/code> assembly. After a bit of digging, we can determine that <code>HttpContent.CopyToAsync<\/code> calls an abstract method <code>SerializeToStreamAsync<\/code>. In the case of <code>StringContent<\/code>, that method is overridden by the base class <code>ByteArrayContent<\/code>, with an implementation similar to the following:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprotected override Task SerializeToStreamAsync(Stream stream, TransportContext context)\r\n{\r\n    return Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, this.content, this.offset, this.count, null);\r\n}\r\n<\/pre>\n<p>So rather than calling <code>WriteAsync<\/code>, it uses the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/asynchronous-programming-patterns\/asynchronous-programming-model-apm\">legacy APM methods<\/a> <code>Begin\/EndWrite<\/code> instead. Since <code>MemoryStream<\/code> does not override the legacy asynchronous methods (only the Task-based <code>Read\/WriteAsync<\/code> are overridden), the thread pool implementation from the <code>Stream<\/code> base class takes over.<\/p>\n<p>For users of .NET 4.7.1 and below, the above situation holds. However, .NET Core 2.0 in its typical fashion has made some advances. From the <a href=\"https:\/\/github.com\/dotnet\/corefx\/blob\/master\/src\/System.Net.Http\/src\/System\/Net\/Http\/ByteArrayContent.cs\">ByteArrayContent source code<\/a>, we can see that <code>SerializeToStreamAsync<\/code> uses <code>WriteAsync<\/code> instead. Thus, until and unless you switch to CoreFX, mind the thread switch from <code>StringContent<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a previous post, I introduced the concept of &#8220;async holes&#8221; &#8212; those unexpected gaps and obstacles when using asynchronous APIs in .NET. This time I will tell the tale of the async hole in StringContent. System.Net.Http.StringContent is the derived&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,41],"tags":[],"class_list":["post-5392","post","type-post","status-publish","format-standard","hentry","category-async","category-tdd"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5392","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=5392"}],"version-history":[{"count":1,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5392\/revisions"}],"predecessor-version":[{"id":5393,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5392\/revisions\/5393"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5392"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5392"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5392"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}