NDepend

Improve your .NET code quality with NDepend

Migrating Delegate.BeginInvoke Calls to .NET Core, .NET 5 and .NET 6

Migrating Delegate.BeginInvoke Calls to .NET Core, .NET 5 and .NET 6

In this 2019 post, the .NET Base Class Library engineers announced that the good old Delegate.BeginInvoke .NET Framework syntax wasn’t supported in .NET Core and consequently in .NET 5, 6 … The reasons for this are twofold:

  1. The Task-based Asynchronous Pattern (TAP) is the recommended async model as of .NET Framework 4.5.
  2. The implementation of async delegates depends on remoting features not present in .NET Core,

The detailed discussion about this decision can be found here https://github.com/dotnet/runtime/issues/16312.

In the same post a code sample is provided to achieve the Delegate.BeginInvoke features with the TAP and the async/await keywords. I don’t find this solution satisfying because it requires significant refactoring for porting some Delegate.BeginInvoke calls to .NET Core.

Let’s see how we can implement the two primary Delegate.BeginInvoke usage scenarios with .NET Standard code. The key is to keep a syntax similar enough to simplify the migration from .NET Fx to .NET Core/5/6.

Scenario 1: Call EndInvoke() and on IAsyncResult object returned by BeginInvoke()

Here is the syntax we can achieve with the code below:

Find below the whole implementation followed by unit tests that 100% test it. Here are some remarks:

  • Ideally we would have wanted a syntax like FuncDouble.MyBeginInvoke(11) but there is no C# delegate type inference at call site. Thus we need to use new Func<int, int>(FuncDouble).MyBeginInvoke(11) instead.
  • A trivial MyBeginInvoke() overload is needed for each Func<T0,...,TN,Result> cardinality N.
  • I’ve never been a fan of Delegate.EndInvoke() re-throwing an exception thrown while executing the asynchronous procedure. Thus IMyAsyncResult<TResult> presents a bool TryEndInvoke(out TResult result, out Exception exception) method that returns false and the exception when failed. This discards the need for a try{ } catch{ } clause when calling EndInvoke().
  • TryEndInvoke() is called on the IMyAsyncResult object and not on the delegate object as originally. This syntax is easier since only one object needs to be provided instead of two to conclude the async task.
  • The key of this implementation is the private nested class MyAsyncResult<TResult> that keeps a reference to the async task and waits for termination upon a TaskAwaiter object.
  • Finally, notice that TryEndInvoke() can be called only once. An InvalidOperationException is thrown the second time it is called.

Here are the tests that challenge all possible paths. Thanks to some usage of Thread.CurrentThread.ManagedThreadId these tests check that the asynchronous procedure is actually executed on a background thread.

Scenario 2: Call an On-Task-Completed-Action once the asynchronous call is terminated

The second usual scenario to achieve is to provide an On-Task-Completed-Action to consume the result – or eventually the exception thrown – instead of calling an EndInvoke() method.

Here is the code to achieve that. The astute is to rely on Task.ContinueWith().

Here are the tests that fully cover this implementation.

Conclusion

Migrating .NET Fx calls to Delegate.BeginInvoke() to .NET Core .NET 5/6 can be tricky. Hopefully the code provided in this post greatly simplifies this task.

My dad being an early programmer in the 70's, I have been fortunate to switch from playing with Lego, to program my own micro-games, when I was still a kid. Since then I never stop programming.

I graduated in Mathematics and Software engineering. After a decade of C++ programming and consultancy, I got interested in the brand new .NET platform in 2002. I had the chance to write the best-seller book (in French) on .NET and C#, published by O'Reilly and also did manage some academic and professional courses on the platform and C#.

Over my consulting years I built an expertise about the architecture, the evolution and the maintenance challenges of large & complex real-world applications. It seemed like the spaghetti & entangled monolithic legacy concerned every sufficiently large team. As a consequence, I got interested in static code analysis and started the project NDepend in 2004.

Nowadays NDepend is a full-fledged Independent Software Vendor (ISV). With more than 12.000 client companies, including many of the Fortune 500 ones, NDepend offers deeper insight and full control on their application to a wide range of professional users around the world.

I live with my wife and our twin kids Léna and Paul in the beautiful island of Mauritius in the Indian Ocean.