{"id":109955,"date":"2024-07-04T07:00:00","date_gmt":"2024-07-04T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109955"},"modified":"2024-06-24T09:14:47","modified_gmt":"2024-06-24T16:14:47","slug":"20240604-00-2","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240704-00\/?p=109955","title":{"rendered":"How do I produce a Windows Runtime asynchronous activity from C#?"},"content":{"rendered":"<p>Last time, we looked at <a title=\"How do I produce a Windows Runtime asynchronous activity from C++\/CX?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109953\"> how to produce a Windows Runtime asynchronous activity in C++\/CX<\/a>. Doing it in C# is similar: The method that converts a C# <code>Task<\/code> to a Windows Runtime asynchronous activity is <code>AsyncInfo.<wbr \/>Run<\/code>, and just like PPL&#8217;s <code>create_<wbr \/>async<\/code>, it infers the resulting asynchronous activity from the signature of the object you pass to it.<\/p>\n<p>Given a lambda that returns a type <code>R<\/code> and takes the parameters <code>Params...<\/code>, the <code>AsyncInfo.<wbr \/>Run<\/code> method returns the following Windows Runtime interface:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th rowspan=\"2\">\u00a0<\/th>\n<th colspan=\"2\">Params&#8230;<\/th>\n<\/tr>\n<tr>\n<td><code>(Cancellation\u00adToken)<\/code><\/td>\n<td><code>(Cancellation\u00adToken, IProgress&lt;P&gt;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>R<\/code> = <code>Task<\/code><\/td>\n<td><code>IAsyncAction<\/code><\/td>\n<td><code>IAsyncActionWithProgress&lt;P&gt;<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>R<\/code> = <code>Task&lt;T&gt;<\/code><\/td>\n<td><code>IAsyncOperation&lt;T&gt;<\/code><\/td>\n<td><code>IAsyncOperationWithProgress&lt;T, P&gt;<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>This is basically the same table that we had for <code>create_<wbr \/>async<\/code>, but with three significant changes:<\/p>\n<ol>\n<li>The lambda must accept at least a <code>Cancellation\u00adToken<\/code>. (It was optional for <code>create_<wbr \/>async<\/code>.)<\/li>\n<li>The lambda must return a <code>Task<\/code> or <code>Task&lt;T&gt;<\/code>. (The <code>create_<wbr \/>async<\/code> allowed you to return <code>void<\/code> or <code>T<\/code>.)<\/li>\n<li>The lambda&#8217;s cancellation token and progress reporter parameters are in opposite order!<\/li>\n<\/ol>\n<p>As with <code>create_<wbr \/>async<\/code>, the lambda can use the <code>IProgress&lt;P&amp;gt<\/code> <code>progress_<wbr \/>reporter&lt;P&gt;<\/code> to produce progress reports, and the <code>Cancellation\u00adToken<\/code> to detect whether the asynchronous activity has been canceled.<\/p>\n<p>Here&#8217;s the simplest case: An <code>IAsyncAction<\/code>.<\/p>\n<pre>Task&lt;Widget&gt; GetWidgetAsync(string id);\r\nTask EnableWidgetAsync(Widget widget, bool enable);\r\n\r\nIAsyncAction EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return AsyncInfo.Run(async (cancel) =&gt; {\r\n        var widget = await GetWidgetAsync(id);\r\n        cancel.ThrowIfCancellationRequested();\r\n        await EnableWidgetAsync(widget, enable);\r\n    });\r\n}\r\n<\/pre>\n<p>And with progress:<\/p>\n<pre>IAsyncActionWithProgress&lt;int&gt; EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return AsyncInfo.Run(async (CancellationToken cancel,\r\n                                IProgress&lt;int&gt; progress) =&gt; {\r\n        progress.Report(0);\r\n        var widget = await GetWidgetAsync(id);\r\n        cancel.ThrowIfCancellationRequested();\r\n        progress.Report(1);\r\n        await EnableWidgetAsync(widget, enable);\r\n    });\r\n}\r\n<\/pre>\n<p>The compiler can&#8217;t infer what the <code>P<\/code> is for the <code>IProgress&lt;P&gt;<\/code> parameter, so you have to specify it explicitly. And then you run into another rule that says that if you provide an explicit type for one lambda parameter, you must do so for all of them, so we&#8217;re stuck typing <code>Cancellation\u00adToken<\/code> again.<\/p>\n<p>But wait, there&#8217;s an easier way: Instead of helping the compiler infer <code>IProgress&lt;P&gt;<\/code>, we explicitly specialize the <code>Run<\/code> method.<\/p>\n<pre>IAsyncActionWithProgress&lt;int&gt; EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return AsyncInfo.Run<span style=\"border: solid 1px currentcolor;\">&lt;int&gt;<\/span>(async (cancel, progress) =&gt; {\r\n        progress.Report(0);\r\n        var widget = await GetWidgetAsync(id);\r\n        cancel.ThrowIfCancellationRequested();\r\n        progress.Report(1);\r\n        await EnableWidgetAsync(widget, enable);\r\n    });\r\n}\r\n<\/pre>\n<p>May as well do the async operations, too.<\/p>\n<pre>IAsyncOperation&lt;bool&gt; EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return AsyncInfo.Run(async (cancel) =&gt; {\r\n        var widget = await GetWidgetAsync(id);\r\n        cancel.ThrowIfCancellationRequested();\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (widget == null) return false;       <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">await EnableWidgetAsync(widget, enable);<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">return true;                            <\/span>\r\n    });\r\n}\r\n\r\nIAsyncOperationWithProgress&lt;bool, int&gt; EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return AsyncInfo.Run&lt;bool, int&gt;(async (cancel, progress) =&gt; {\r\n        progress.Report(0);\r\n        var widget = await GetWidgetAsync(id);\r\n        cancel.ThrowIfCancellationRequested();\r\n        progress.Report(1);\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (widget == null) return false;       <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">await EnableWidgetAsync(widget, enable);<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">return true;                            <\/span>\r\n    });\r\n}\r\n<\/pre>\n<p>Now, in the case where you don&#8217;t support cancellation or progress, you have an alternative: For the case of <code>IAsyncAction<\/code>, you can create a <code>Task<\/code> and then call its <code>AsAsyncAction<\/code> method, and in the case of <code>IAsyncOperation&lt;T&gt;<\/code>, you call its its <code>AsAsyncOperation<\/code> method. These aren&#8217;t true methods on <code>Task<\/code> but rather extension methods provided by the <code>System.<wbr \/>Windows\u00adRuntime\u00adSystem\u00adExtensions<\/code> namespace, so you may need to add a <code>using System.<wbr \/>Windows\u00adRuntime\u00adSystem\u00adExtensions;<\/code> to your file.<\/p>\n<pre>using System.WindowsRuntimeSystemExtensions;\r\n\r\nIAsyncAction EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    Func&lt;Task&gt; taskMaker = async () =&gt; {\r\n        var widget = await GetWidgetAsync(id);\r\n        await EnableWidgetAsync(widget, enable);\r\n    };\r\n    return taskMaker().AsAsyncAction();\r\n}\r\n\r\nIAsyncOperation&lt;int&gt; EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    Func&lt;Task&lt;int&gt;&gt; taskMaker = async () =&gt; {\r\n        var widget = await GetWidgetAsync(id);\r\n        if (widget == null) return false;\r\n        await EnableWidgetAsync(widget, enable);\r\n        return true;\r\n    };\r\n    return taskMaker().AsAsyncOperation();\r\n}\r\n<\/pre>\n<p>These conversions are more convenient to use if you just turn the lambda into its own named function:<\/p>\n<pre>\/\/ IAsyncAction version\r\nasync Task EnableWidgetByIdTaskAsync(string id, bool enable)\r\n{\r\n    var widget = await GetWidgetAsync(id);\r\n    await EnableWidgetAsync(widget, enable);\r\n}\r\n\r\nIAsyncAction EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return EnableWidgetByIdTaskAsync(id, enable).AsAsyncAction();\r\n}\r\n\r\n\/\/ IAsyncOperation version\r\nasync Task&lt;bool&gt; EnableWidgetByIdTaskAsync(string id, bool enable)\r\n{\r\n    var widget = await GetWidgetAsync(id);\r\n    if (widget == null) return false;\r\n    await EnableWidgetAsync(widget, enable);\r\n    return true;\r\n}\r\n\r\nIAsyncOperation&lt;int&gt; EnableWidgetByIdAsync(string id, bool enable)\r\n{\r\n    return EnableWidgetByIdTaskAsync(id, enable).AsAsyncOperation();\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>The AsyncInfo helper class converts Tasks to Windows Runtime asynchronous activities.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109955","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The AsyncInfo helper class converts Tasks to Windows Runtime asynchronous activities.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109955","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109955"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109955\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109955"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109955"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109955"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}