Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrap .NET Remoting async method in TPL Task

We have a legacy .NET Remoting-based app. Our client client libary currently supports only synchronous operations. I would like to add asynchronous operations with TPL-based async Task<> methods.

As proof of concept, I have set up a basic remoting server/client solution based a modified version of these instructions.

I have also found this article that describes how to convert APM-based asynchronous operations to TPL-based async tasks (using Task.Factory.FromAsync)

What I'm unsure about is whether I'm compelled to specify the callback function in .BeginInvoke() and also to specify the .EndInvoke(). If both are required, what exactly is the difference between the callback function and .EndInvoke(). If only one is required, which one should I use to return values and also ensure that I have no memory leaks.

Here is my current code where I don't pass a callback to .BeginInvoke():

public class Client : MarshalByRefObject
{
    private IServiceClass service;

    public delegate double TimeConsumingCallDelegate();

    public void Configure()
    {
        RemotingConfiguration.Configure("client.exe.config", false);

        var wellKnownClientTypeEntry = RemotingConfiguration.GetRegisteredWellKnownClientTypes()
            .Single(wct => wct.ObjectType.Equals(typeof(IServiceClass)));

        this.service = Activator.GetObject(typeof(IServiceClass), wellKnownClientTypeEntry.ObjectUrl) as IServiceClass;
    }

    public async Task<double> RemoteTimeConsumingRemoteCall()
    {
        var timeConsumingCallDelegate = new TimeConsumingCallDelegate(service.TimeConsumingRemoteCall);

        return await Task.Factory.FromAsync
            (
                timeConsumingCallDelegate.BeginInvoke(null, null),
                timeConsumingCallDelegate.EndInvoke
           );
    }

    public async Task RunAsync()
    {
        var result = await RemoteTimeConsumingRemoteCall();
        Console.WriteLine($"Result of TPL remote call: {result} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
    }
}

public class Program
{
    public static async Task Main(string[] Args)
    {
        Client clientApp = new Client();
        clientApp.Configure();

        await clientApp.RunAsync();

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey(false);
    }
}
like image 374
Francois Botha Avatar asked Nov 05 '18 12:11

Francois Botha


People also ask

Is async await part of TPL?

Await & Async was built on the Task Parallel Library (TPL) which was introduced in the . NET Framework 4. Their purpose is to enable asynchronous programming.

Can we call asynchronous method from another synchronous method?

Use the Result property on the asynchronous Task, like so: // Synchronous method. void Method()

Is Task run async?

NET, Task. Run is used to asynchronously execute CPU-bound code.

Does Task create new thread?

A task can have multiple processes happening at the same time. Threads can only have one task running at a time. We can easily implement Asynchronous using 'async' and 'await' keywords. A new Thread()is not dealing with Thread pool thread, whereas Task does use thread pool thread.

How to use async with Task<T>?

Using Async with Task<T> enables some magic, where objects of type T are automatically wrapped in new Task objects for us. We do not have to manually wrap our objects of Type T into new Task objects before returning them. This automatic wrapping happens regardless of whether or not the method is awaitable (asynchronous) or synchronous.

Is it possible to encapsulate an event-based asynchronous operation in TPL?

The TPL does not provide any methods that are specifically designed to encapsulate an event-based asynchronous operation in the same way that the FromAsync family of methods wrap the IAsyncResult pattern.

Why don’t async methods automatically wrap returned objects into tasks?

It turns out that awaiting execution from methods invoked within the action method really has nothing to do with automagically wrapping returned objects into Tasks. It’s solely the combination of the async keyword with returned <strong>Task<T></strong> that enables this syntactical sugar.

Does returning Task<t> mean the action is asynchronous?

We briefly erroneously assumed returning Task<T> meant asynchronous. If the original action method could be improved: if it did not need to be asynchronous, returning a Task in a synchronous method is required and may cause a performance hit.


Video Answer


2 Answers

The difference between between the callback function and .EndInvoke() is that the callback will get executed on an arbitrary thread from the threadpool. If you must be sure that you obtain result from the read on the same thread as the one you called BeginInvoke, then you should not use the callback, but poll the IAsyncResult object and call .EndInvoke() when the operation is complete.

If you call .EndInvoke() right after .Beginnvoke(), you block the thread until the operation completes. This will work, but will scale poorly.

So, what you do seems all right!

like image 187
Nick Avatar answered Oct 20 '22 03:10

Nick


You must specify the callback function for efficiency reasons. If the FromAsync only has an IAsyncResult to work with it cannot be notified when that async result completes. It will have to use an event to wait. This blocks a thread (or, it registers a threadpool wait maybe which is not so bad).

Efficient async IO requires callbacks at some level.

If you are going async I assume you are doing this because you have many concurrent calls or the calls are very long running. For that reason you should use the more efficient mechanism.

If you don't have many or long running calls then async is not going to help you with performance in any way but it might still make GUI programming easier.

So this is not correct:

public async Task<double> RemoteTimeConsumingRemoteCall()
{
    var timeConsumingCallDelegate = new TimeConsumingCallDelegate(service.TimeConsumingRemoteCall);

    return await Task.Factory.FromAsync
        (
            timeConsumingCallDelegate.BeginInvoke(null, null),
            timeConsumingCallDelegate.EndInvoke
       );
}

To make sure that your implementation indeed does not block any thread I'd do this: Insert a Thread.Sleep(100000) on the server side and issue 1000 concurrent calls on the client. You should find that the thread count does not rise.

like image 42
usr Avatar answered Oct 20 '22 01:10

usr