{"id":22394,"date":"2019-03-28T14:29:50","date_gmt":"2019-03-28T21:29:50","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=22394"},"modified":"2021-03-23T17:15:48","modified_gmt":"2021-03-24T00:15:48","slug":"migrating-delegate-begininvoke-calls-for-net-core","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/migrating-delegate-begininvoke-calls-for-net-core\/","title":{"rendered":"Migrating Delegate.BeginInvoke Calls for .NET Core"},"content":{"rendered":"<p>I recently worked with a couple customers migrating applications to .NET Core that had to make code changes to workaround <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> methods on delegates not being supported on .NET Core. In this post, we&#8217;ll look at why these APIs aren&#8217;t implemented for .NET Core, why their usage isn&#8217;t caught by the <a href=\"https:\/\/github.com\/Microsoft\/dotnet-apiport\">.NET API Portability Analyzer<\/a>, and how to fix code using them to work with .NET Core.<\/p>\n<h2 id=\"abouttheapis\">About the APIs<\/h2>\n<p>As explained in <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/asynchronous-programming-patterns\/calling-synchronous-methods-asynchronously\">.NET documentation<\/a>, the <code>BeginInvoke<\/code> method on delegate types allows them to be invoked asynchronously. <code>BeginInvoke<\/code> immediately (without waiting for the delegate to complete) returns an <code>IAsyncResult<\/code> object that can be used later (by calling <code>EndInvoke<\/code>) to wait for the call to finish and receive its return value.<\/p>\n<p>For example, this code calls the <code>DoWork<\/code> method in the background:<\/p>\n<pre class=\"lang:c# decode:true \">delegate int WorkDelegate(int arg);\r\n...\r\nWorkDelegate del = DoWork;\r\n\r\n\/\/ Calling del.BeginInvoke starts executing the delegate on a\r\n\/\/ separate ThreadPool thread\r\nConsole.WriteLine(\"Starting with BeginInvoke\");\r\nvar result = del.BeginInvoke(11, WorkCallback, null);\r\n\r\n\/\/ This writes output to the console while DoWork is running in the background\r\nConsole.WriteLine(\"Waiting on work...\");\r\n\r\n\/\/ del.EndInvoke waits for the delegate to finish executing and \r\n\/\/ gets its return value\r\nvar ret = del.EndInvoke(result);<\/pre>\n<p><strong>The <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/asynchronous-programming-patterns\/asynchronous-programming-model-apm\">Asynchronous Programming Model (APM)<\/a> (using <code>IAsyncResult<\/code> and <code>BeginInvoke<\/code>) is no longer the preferred method of making asynchronous calls.<\/strong> The <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/asynchronous-programming-patterns\/task-based-asynchronous-pattern-tap\">Task-based Asynchronous Pattern (TAP)<\/a> is the recommended async model as of .NET Framework 4.5. Because of this, and because the implementation of async delegates depends on remoting features not present in .NET Core, <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> delegate calls are not supported in .NET Core. This is discussed in GitHub issue <a href=\"https:\/\/github.com\/dotnet\/corefx\/issues\/5940\">dotnet\/corefx #5940<\/a>.<\/p>\n<p>Of course, existing .NET Framework code can continue to use <code>IAsyncResult<\/code> async patterns, but running that code on .NET Core will result in an exception similar to this at runtime:<\/p>\n<pre class=\"lang:default decode:true \">Unhandled Exception: System.PlatformNotSupportedException: Operation is not supported on this platform.\r\n   at BeginInvokeExploration.Program.WorkDelegate.BeginInvoke(Int32 arg, AsyncCallback callback, Object object)<\/pre>\n<h2 id=\"whydoesntapiportcatchthis\">Why doesn&#8217;t ApiPort catch this?<\/h2>\n<p>There are other APIs that are supported on .NET Framework that aren&#8217;t supported on .NET Core, of course. What made this one especially confusing for the customers I worked with was that the <a href=\"https:\/\/github.com\/Microsoft\/dotnet-apiport\">.NET API Portability Analyzer<\/a> didn&#8217;t mention the incompatibility in its report. Following our <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/porting\/\">migration guidance<\/a>, the customers had run the API Port tool to spot any APIs used by their projects that weren&#8217;t available on .NET Core. <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> weren&#8217;t reported.<\/p>\n<p>The reason for this is that <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> methods on user-defined delegate types aren&#8217;t actually defined in .NET Framework libraries. Instead, these methods are emitted by the compiler (see the &#8216;Important&#8217; note in <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/asynchronous-programming-patterns\/asynchronous-programming-using-delegates\">Asynchronous Programming Using Delegates<\/a>) as part of building code that declares a delegate type.<\/p>\n<p>In the code above, the <code>WorkDelegate<\/code> delegate type is declared in the C# code. The IL of the compiled library includes <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> methods, added by the compiler:<\/p>\n<pre class=\"lang:default decode:true \">.class auto ansi sealed nested private WorkDelegate\r\n        extends [mscorlib]System.MulticastDelegate\r\n{\r\n.method public hidebysig specialname rtspecialname \r\n        instance void  .ctor(object 'object',\r\n                                native int 'method') runtime managed\r\n{\r\n} \/\/ end of method WorkDelegate::.ctor\r\n\r\n.method public hidebysig newslot virtual \r\n        instance int32  Invoke(int32 arg) runtime managed\r\n{\r\n} \/\/ end of method WorkDelegate::Invoke\r\n\r\n.method public hidebysig newslot virtual \r\n        instance class [mscorlib]System.IAsyncResult \r\n        BeginInvoke(int32 arg,\r\n                    class [mscorlib]System.AsyncCallback callback,\r\n                    object 'object') runtime managed\r\n{\r\n} \/\/ end of method WorkDelegate::BeginInvoke\r\n\r\n.method public hidebysig newslot virtual \r\n        instance int32  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed\r\n{\r\n} \/\/ end of method WorkDelegate::EndInvoke\r\n\r\n} \/\/ end of class WorkDelegate<\/pre>\n<p>&nbsp;<\/p>\n<p>The methods have no implementation because the CLR provides them at runtime.<\/p>\n<p>The .NET Portability Analyzer only analyzes calls made to methods declared in .NET Framework assemblies, so it misses these methods, even though they may feel like .NET dependencies. Because the Portability Analyzer decides which APIs to analyze by looking at the name and public key token of the assembly declaring the API, the only way to analyze <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> methods on user-defined delegates would be to analyze <em>all<\/em> API calls, which would require a large change to the portability analyzer and would have undesirable performance drawbacks.<\/p>\n<h2 id=\"howtoremovebegininvokeendinvokeusage\">How to remove BeginInvoke\/EndInvoke usage<\/h2>\n<p>The good news here is that calls to <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> are usually easy to update so that they work with .NET Core. When fixing this type of error, there are a couple approaches.<\/p>\n<p>First, if the API being invoked with the <code>BeginInvoke<\/code> call has a <code>Task<\/code>-based asynchronous alternative, call that instead. All delegates expose <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> APIs, so there&#8217;s no guarantee that the work is actually done asynchronously (<code>BeginInvoke<\/code> may just invoke a synchronous workflow on a different thread). If the API being called has an async alternative, using that API will probably be the easiest and most performant fix.<\/p>\n<p>If there are no Task-based alternatives available, but offloading the call to a thread pool thread is still useful, this can be done by using <code>Task.Run<\/code> to schedule a task for running the method. If an <code>AsyncCallback<\/code> parameter was supplied when calling <code>BeginInvoke<\/code>, that can be replaced with a call to <code>Task.ContinueWith<\/code>.<\/p>\n<p>Task-based Asynchronous Pattern (TAP) documentation has <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/asynchronous-programming-patterns\/interop-with-other-asynchronous-patterns-and-types\">guidance on how to wrap <code>IAsyncResult<\/code>-style patterns as <code>Task<\/code>s<\/a> using <code>TaskFactory<\/code>. Unfortunately, that solution doesn&#8217;t work for .NET Core because the APM APIs (<code>BeginInvoke<\/code>, <code>EndInvoke<\/code>) are still used inside the wrapper. The TAP documentation guidance is useful for using older APM-style code in .NET Framework TAP scenarios, but for .NET Core migration, APM APIs like <code>BeginInvoke<\/code> and <code>EndInvoke<\/code> need to be replaced with synchronous calls (like <code>Invoke<\/code>) which can be run on a separate thread using <code>Task.Run<\/code>.<\/p>\n<p>As an example, the code from earlier in this post can be replaced with the following:<\/p>\n<pre class=\"lang:c# decode:true\">delegate int WorkDelegate(int arg);\r\n...\r\nWorkDelegate del = DoWork;\r\n\r\n\/\/ Schedule the work using a Task and \r\n\/\/ del.Invoke instead of del.BeginInvoke.\r\nConsole.WriteLine(\"Starting with Task.Run\");\r\nvar workTask = Task.Run(() =&gt; del.Invoke(11));\r\n\r\n\/\/ Optionally, we can specify a continuation delegate \r\n\/\/ to execute when DoWork has finished.\r\nvar followUpTask = workTask.ContinueWith(TaskCallback);\r\n\r\n\/\/ This writes output to the console while DoWork is running in the background.\r\nConsole.WriteLine(\"Waiting on work...\");\r\n\r\n\/\/ We await the task instead of calling EndInvoke.\r\n\/\/ Either workTask or followUpTask can be awaited depending on which\r\n\/\/ needs to be finished before proceeding. Both should eventually\r\n\/\/ be awaited so that exceptions that may have been thrown can be handled.\r\nvar ret = await workTask;\r\nawait followUpTask;<\/pre>\n<p>This code snippet provides the same functionality and works on .NET Core.<\/p>\n<h2 id=\"resources\">Resources<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/parallel-programming\/task-based-asynchronous-programming\">Task-based asynchronous programming (TAP)<\/a> (the preferred alternative to <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/asynchronous-programming-patterns\/asynchronous-programming-model-apm\">APM<\/a>)<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/platform-compat\">Platform Compatibility Analyzers<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Microsoft\/dotnet-apiport\">API Portability Analyzer<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I recently worked with a couple customers migrating applications to .NET Core that had to make code changes to workaround BeginInvoke and EndInvoke methods on delegates not being supported on .NET Core. In this post, we&#8217;ll look at why these APIs aren&#8217;t implemented for .NET Core, why their usage isn&#8217;t caught by the .NET API [&hellip;]<\/p>\n","protected":false},"author":7413,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,196,195],"tags":[],"class_list":["post-22394","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-dotnet-core","category-dotnet-framework"],"acf":[],"blog_post_summary":"<p>I recently worked with a couple customers migrating applications to .NET Core that had to make code changes to workaround BeginInvoke and EndInvoke methods on delegates not being supported on .NET Core. In this post, we&#8217;ll look at why these APIs aren&#8217;t implemented for .NET Core, why their usage isn&#8217;t caught by the .NET API [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/22394","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\/7413"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=22394"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/22394\/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=22394"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=22394"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=22394"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}