{"id":55915,"date":"2010-04-23T09:42:00","date_gmt":"2010-04-23T09:42:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/pfxteam\/2010\/04\/23\/parallelextensionsextras-tour-12-asynccache\/"},"modified":"2010-04-23T09:42:00","modified_gmt":"2010-04-23T09:42:00","slug":"parallelextensionsextras-tour-12-asynccache","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/parallelextensionsextras-tour-12-asynccache\/","title":{"rendered":"ParallelExtensionsExtras Tour &#8211; #12 &#8211; AsyncCache"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\"><em><span lang=\"EN\">(The full set of ParallelExtensionsExtras Tour posts is available <a href=\"https:\/\/blogs.msdn.com\/pfxteam\/archive\/2010\/04\/04\/9990342.aspx\">here<\/a>.)<\/span><\/em><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><em><span lang=\"EN\"><\/span><\/em><\/font><font size=\"3\"><font face=\"Calibri\">Caches are ubiquitous in computing, serving as a staple of both hardware architecture and software development.<span>&nbsp; <\/span>In software, caches are often implemented as dictionaries, where some data is retrieved or computed based on a key, and then that key and its resulting data\/value are added to the dictionary.<span>&nbsp; P<\/span>rior to re-retrieving or re-computing the value for a given key, we can first check the dictionary\/cache to see whether we&rsquo;ve already done so, and if we have, we simply copy the element from the dictionary.<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">As we all know, a multithreaded environment can bring with it many challenges, and such challenges apply to caches as well.<span>&nbsp; <\/span>Imagine creating a cache to store downloaded web pages.<span>&nbsp; <\/span>If multiple threads are trying to access the cache at the same time, we not only want to make sure that they don&rsquo;t corrupt the employed data structures, we also want to make sure that they&rsquo;re not doing more work than they need to: if two threads need the same page downloaded, just download it once rather than twice, and give them both copies.<span>&nbsp; <\/span>In this fashion, we need a form of an asynchronous cache that allows threads to get back a handle for the thing in the cache they want, a handle that, for example, will then provide them with a callback notification when the download has completed or that will allow them to wait for the download to complete.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><span><\/span>The AsyncCache class in AsyncCache.cs in ParallelExtensionsExtras provides this support, and it may surprise you just how little code is required to do this, taking advantage of the new concurrency support in .NET 4. <\/font><\/font><font size=\"3\"><font face=\"Calibri\">The type is defined as follows:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public class AsyncCache&lt;TKey, TValue&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">and contains two fields.<span>&nbsp; <\/span>The first field is a delegate that will be invoked for a key when that key is requested and is not yet in the dictionary; it is this delegate that produces the value for the key:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">private readonly Func&lt;TKey, Task&lt;TValue&gt;&gt; _valueFactory;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">Note that this isn&rsquo;t a Func&lt;TKey,TValue&gt;, but rather a Func&lt;TKey,Task&lt;TValue&gt;&gt;.<span>&nbsp; <\/span>The function is supplied by the user to the AsyncCache constructor and produces a task that represents the retrieval of the value for a given key.<span>&nbsp; <\/span>This task could either be computational in nature (e.g. one created by Task.Factory.StartNew), or it could be async I\/O-based, such as a task representing a download from a web site.<span>&nbsp; <\/span>Either way, it&rsquo;s this task that&rsquo;s stored in the cache in the second field: <\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">private readonly ConcurrentDictionary&lt;TKey, Lazy&lt;Task&lt;TValue&gt;&gt;&gt; _map;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">As you might have guessed, we&rsquo;re using a ConcurrentDictionary as the storage for the cache, which helps to&nbsp;ensure that multiple threads may access the cache concurrently without corrupting the internals of the data store.<span>&nbsp; <\/span>As noted earlier, the function to generate values for keys returns tasks, but the dictionary&rsquo;s value isn&rsquo;t just Task&lt;TValue&gt;, it&rsquo;s Lazy&lt;Task&lt;TValue&gt;&gt;.<span>&nbsp; <\/span>The addition of the Lazy&lt;&gt; here makes it really easy to ensure that only one task is generated for any one key, avoiding any races that might otherwise result.<span>&nbsp; <\/span>We can see this by looking at the most important method on AsyncCache:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public Task&lt;TValue&gt; GetValue(TKey key)<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>var value = new Lazy&lt;Task&lt;TValue&gt;&gt;(() =&gt; _valueFactory(key));<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>return _map.GetOrAdd(key, value).Value;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">You&rsquo;ve now seen almost all of AsyncCache&rsquo;s implementation&hellip; everything else in the type is really secondary (e.g. implementing the ICollection interface).<span>&nbsp; <\/span>GetValue simply creates a new Lazy&lt;Task&lt;TValue&gt;&gt; that will run the _valueFactory when invoked.<span>&nbsp; <\/span>The method then checks whether the dictionary already has a Lazy&lt;&gt; for this key, adding the one we just created if it didn&rsquo;t yet have one, and regardless returning the Value of whatever Lazy&lt;&gt; we got back.<span>&nbsp; <\/span>By accessing the Lazy&lt;Task&lt;TValue&gt;&gt;&rsquo;s Value, we get back the task for this key, and that&rsquo;s handed back to the caller. The caller now has a Task&lt;TValue&gt; for the supplied TKey, and as with any other task, the caller can use ContinueWith to be notified when the task has completed, can Wait on the task to block until the task has completed, or can simply use its Result property to get at the data when it&rsquo;s available (potentially blocking in the process).<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">With AsyncCache&lt;TKey,TValue&gt; in place, it&rsquo;s now straightforward to either use it as is, or to create specialized variants of the cache. For example, in our earlier problem statement we described wanting to be able to cache downloaded web pages.<span>&nbsp; <\/span>Here&rsquo;s the complete implementation of that:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">public sealed class HtmlAsyncCache : AsyncCache&lt;Uri, string&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>public HtmlAsyncCache() : <\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>base(uri =&gt; new WebClient().DownloadStringTask(uri)) { }<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">}<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">The DownloadStringTask extension method on WebClient is another method defined in ParallelExtensionExtras, and we&rsquo;ll get to that another day.<span>&nbsp; <\/span>Suffice it to say that this method returns a Task&lt;string&gt; that represents the asynchronous downloading of a web page at the specified Uri.<span>&nbsp; <\/span>As such, our HtmlAsyncCache is simply a derived AsyncCache&lt;Uri,string&gt;, where the valueFactory calls DownloadStringTask for the supplied key\/uri.<\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">A consumer of this type may request a particular page:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">HtmlAsyncCache cache = new HtmlAsyncCache();<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&#8230;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">Task&lt;string&gt; page = <\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; cache.GetValue(new Uri(&ldquo;http:\/\/www.microsoft.com&rdquo;));<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">and then either use its value directly, blocking if it&rsquo;s not yet available:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">Console.WriteLine(page.Result);<\/p>\n<p><\/font><\/p>\n<p class=\"Code\">\n<p><font face=\"Consolas\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">or ask to be notified when the value is available:<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">page.ContinueWith(completed =&gt; <\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; Console.WriteLine(completed.Result));<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p><\/font>&nbsp;<\/p>\n<p><font face=\"Calibri\"><\/p>\n<p class=\"MsoNormal\"><font size=\"3\">And if you wanted to download multiple pages and only do something when you had all three, that&rsquo;s easy as well.<span>&nbsp; <\/span>Since our asynchronous operations are represented as tasks, we can use the combinators provided by Task for this purpose, e.g.<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">var page1 = cache.GetValue(<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; new Uri(&ldquo;http:\/\/msdn.microsoft.com\/pfxteam&rdquo;));<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">var page2 = cache.GetValue(<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; new Uri(&ldquo;https:\/\/msdn.com\/concurrency&rdquo;));<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">var page3 = cache.GetValue(<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; new Uri(&ldquo;http:\/\/www.microsoft.com&rdquo;));<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><\/p>\n<p><\/font>&nbsp;<\/p>\n<p class=\"Code\"><font face=\"Consolas\">Task.Factory.ContinueWhenAll(<\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">&nbsp;&nbsp;&nbsp; new [] { page1, page2, page3 }, completedPages =&gt;<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">{<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\"><span>&nbsp;&nbsp;&nbsp; <\/span>&hellip; \/\/ use the downloaded pages here<\/p>\n<p><\/font><\/p>\n<p class=\"Code\"><font face=\"Consolas\">});<\/font><\/p>\n<p class=\"Code\"><font size=\"3\"><em><\/em><\/font><\/p>\n<p><\/font><\/p>\n<p>&nbsp;\n<\/p>\n<p class=\"MsoNormal\"><i><font size=\"3\" face=\"Calibri\">(Thanks go to <\/font><a href=\"http:\/\/lucabolognese.wordpress.com\/\"><font size=\"3\" face=\"Calibri\">Luca Bolognese<\/font><\/a><font size=\"3\"><font face=\"Calibri\"> for originally supplying the idea for AsyncCache.)<\/p>\n<p><\/font><\/font><\/i><\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>(The full set of ParallelExtensionsExtras Tour posts is available here.) Caches are ubiquitous in computing, serving as a staple of both hardware architecture and software development.&nbsp; In software, caches are often implemented as dictionaries, where some data is retrieved or computed based on a key, and then that key and its resulting data\/value are added [&hellip;]<\/p>\n","protected":false},"author":360,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[7908],"tags":[7907,7916,7909,7924,7912],"class_list":["post-55915","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-pfxteam","tag-net-4","tag-coordination-data-structures","tag-parallel-extensions","tag-parallelextensionsextras","tag-task-parallel-library"],"acf":[],"blog_post_summary":"<p>(The full set of ParallelExtensionsExtras Tour posts is available here.) Caches are ubiquitous in computing, serving as a staple of both hardware architecture and software development.&nbsp; In software, caches are often implemented as dictionaries, where some data is retrieved or computed based on a key, and then that key and its resulting data\/value are added [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/55915","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/360"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=55915"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/55915\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=55915"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=55915"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=55915"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}