Migrating Delegate.BeginInvoke Calls for .NET Core

Avatar

Mike

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’ll look at why these APIs aren’t implemented for .NET Core, why their usage isn’t caught by the .NET API Portability Analyzer, and how to fix code using them to work with .NET Core.

About the APIs

As explained in .NET documentation, the BeginInvoke method on delegate types allows them to be invoked asynchronously. BeginInvoke immediately (without waiting for the delegate to complete) returns an IAsyncResult object that can be used later (by calling EndInvoke) to wait for the call to finish and receive its return value.

For example, this code calls the DoWork method in the background:

The Asynchronous Programming Model (APM) (using IAsyncResult and BeginInvoke) is no longer the preferred method of making asynchronous calls. The Task-based Asynchronous Pattern (TAP) 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, BeginInvoke and EndInvoke delegate calls are not supported in .NET Core. This is discussed in GitHub issue dotnet/corefx #5940.

Of course, existing .NET Framework code can continue to use IAsyncResult async patterns, but running that code on .NET Core will result in an exception similar to this at runtime:

Why doesn’t ApiPort catch this?

There are other APIs that are supported on .NET Framework that aren’t supported on .NET Core, of course. What made this one especially confusing for the customers I worked with was that the .NET API Portability Analyzer didn’t mention the incompatibility in its report. Following our migration guidance, the customers had run the API Port tool to spot any APIs used by their projects that weren’t available on .NET Core. BeginInvoke and EndInvoke weren’t reported.

The reason for this is that BeginInvoke and EndInvoke methods on user-defined delegate types aren’t actually defined in .NET Framework libraries. Instead, these methods are emitted by the compiler (see the ‘Important’ note in Asynchronous Programming Using Delegates) as part of building code that declares a delegate type.

In the code above, the WorkDelegate delegate type is declared in the C# code. The IL of the compiled library includes BeginInvoke and EndInvoke methods, added by the compiler:

 

The methods have no implementation because the CLR provides them at runtime.

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 BeginInvoke and EndInvoke methods on user-defined delegates would be to analyze all API calls, which would require a large change to the portability analyzer and would have undesirable performance drawbacks.

How to remove BeginInvoke/EndInvoke usage

The good news here is that calls to BeginInvoke and EndInvoke are usually easy to update so that they work with .NET Core. When fixing this type of error, there are a couple approaches.

First, if the API being invoked with the BeginInvoke call has a Task-based asynchronous alternative, call that instead. All delegates expose BeginInvoke and EndInvoke APIs, so there’s no guarantee that the work is actually done asynchronously (BeginInvoke 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.

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 Task.Run to schedule a task for running the method. If an AsyncCallback parameter was supplied when calling BeginInvoke, that can be replaced with a call to Task.ContinueWith.

Task-based Asynchronous Pattern (TAP) documentation has guidance on how to wrap IAsyncResult-style patterns as Tasks using TaskFactory. Unfortunately, that solution doesn’t work for .NET Core because the APM APIs (BeginInvoke, EndInvoke) 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 BeginInvoke and EndInvoke need to be replaced with synchronous calls (like Invoke) which can be run on a separate thread using Task.Run.

As an example, the code from earlier in this post can be replaced with the following:

This code snippet provides the same functionality and works on .NET Core.

Resources

Avatar
Mike Rousos

Follow Mike   

3 comments

  • Avatar
    Evaldas Jocys

    Basically
    old: BeginInvoke(Action method)new: Task.Factory.StartNew(method, CancellationToken.None, TaskCreationOptions.DenyChildAttach, MainScheduler);
    old: Invoke(Action method)new: new Task(method).RunSynchronously(MainScheduler);
    —————–

    This is the closest example of Invoke and BeginInvoke replacement I could think of.
    public MainForm(){InitializeComponent();Console.WriteLine(“Application on Thread {0}”, Thread.CurrentThread.ManagedThreadId);// Set scheduler which will be used to run tasks on main UI thread.MainScheduler = TaskScheduler.FromCurrentSynchronizationContext();_BeginInvoke(() => { Console.WriteLine(“Executing on Main Thread {0}”, Thread.CurrentThread.ManagedThreadId); });Console.WriteLine(“Task 1 Started”);// Run long task on new thread but update progress on main UI thread.Task.Run(() => { LongTask(); });Console.WriteLine(“Task 2 Started”);}
    TaskScheduler MainScheduler;
    Task _BeginInvoke(Action method){return Task.Factory.StartNew(method, CancellationToken.None, TaskCreationOptions.DenyChildAttach, MainScheduler);}
    void _Invoke(Action method){new Task(method).RunSynchronously(MainScheduler);}
    string LongTask(){Console.WriteLine(“Executing on New Thread {0}”, Thread.CurrentThread.ManagedThreadId);_Invoke(() =>{Console.WriteLine(“Progress on UI Thread {0}”, Thread.CurrentThread.ManagedThreadId);StatusLabel.Text = “Updated”;});Thread.Sleep(500);Console.WriteLine(“Completing on New Thread {0}”, Thread.CurrentThread.ManagedThreadId);return “Done”;}

  • Avatar
    Kula, Blazej

    Mike, great post. I have one question, you write that:
    All delegates expose BeginInvoke and EndInvoke APIs, so there’s no guarantee that the work is actually done asynchronously (BeginInvoke may just invoke a synchronous workflow on a different thread).
    I conceptually understand what this means: this different, still synchronous, thread may be blocked waiting for some operation to finish (e.g.: network query/disc access).
    Is my understanding correct?
    Is there other reason to have this distinction?

  • Anuja Kulkarni
    Anuja Kulkarni

    Hi Mike ,
    Your post has helped me to get an idea about an alternate way . I am converting .Netframework project into .netcore where it was not supporting AsyncResult .This is the method where the beginInvoke starts and the asyncCallBack method uses AsyncResult
    public void UploadBlobAsync(ICloudBlob blob, byte[] mBytes, string fileName){_transferType = TransferTypeEnum.Upload;_blob = blob as CloudBlockBlob;_mBytes = mBytes;_fileName = fileName;
    BlobTransferWorkerDelegate worker = UploadBlobWorker;//References a method to be called when a corresponding asynchronous operation completes.AsyncCallback completedCallback = TaskCompletedCallback;
    lock (_sync){if (IsBusy){throw new InvalidOperationException(“The control is currently busy.”);}AsyncOperation async = AsyncOperationManager.CreateOperation(null);MyAsyncContext context = new MyAsyncContext();bool cancelled;worker.BeginInvoke(context, out cancelled, async, completedCallback, async);IsBusy = true;_taskContext = context;}}
    //async callback method
    private void TaskCompletedCallback(IAsyncResult ar){// get the original worker delegate and the AsyncOperation instanceBlobTransferWorkerDelegate worker = (BlobTransferWorkerDelegate) ((AsyncResult) ar).AsyncDelegate;AsyncOperation async = (AsyncOperation) ar.AsyncState;
    bool cancelled;
    // finish the asynchronous operationworker.EndInvoke(out cancelled, ar);
    // clear the running task flaglock (_sync){IsBusy = false;_taskContext = null;}
    // raise the completed eventAsyncCompletedEventArgs completedArgs = new AsyncCompletedEventArgs(null, cancelled, null);async.PostOperationCompleted(delegate(object e) { OnTaskCompleted((AsyncCompletedEventArgs) e); },completedArgs);}
    Here AsyncResult is not supported in .NetCore. 
    After reaing your post i have tried using Task.Run to invoke, But i am not sure about the correctness of the way i have used .
    public  void UploadBlobAsync(ICloudBlob blob, byte[] mBytes, string fileName){
    BlobTransferWorkerDelegate worker = UploadBlobWorker;AsyncCallback completedCallback = TaskCompletedCallback;AsyncOperation async = AsyncOperationManager.CreateOperation(null);MyAsyncContext context = new MyAsyncContext();bool cancelled;
    Task.Run(() => worker.Invoke(context, out cancelled, async)).ContinueWith(task => completedCallback).Wait();
    }. Is it correct way of writing it ? 

Leave a comment