Wrap .NET Remoting async method in TPL Task
Asked Answered
B

2

6

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);
    }
}
Brucite answered 5/11, 2018 at 12:45 Comment(0)
I
1

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!

Ingham answered 5/11, 2018 at 13:7 Comment(0)
G
1

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.

Guillory answered 5/11, 2018 at 13:1 Comment(0)
I
1

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!

Ingham answered 5/11, 2018 at 13:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.