在 TPL 任务中包装 .NET Remoting 异步方法

Posted

技术标签:

【中文标题】在 TPL 任务中包装 .NET Remoting 异步方法【英文标题】:Wrap .NET Remoting async method in TPL Task 【发布时间】:2018-11-05 12:45:52 【问题描述】:

我们有一个基于 .NET Remoting 的旧版应用程序。我们的客户端客户端库目前仅支持同步操作。我想使用基于 TPL 的 async Task<> 方法添加异步操作。

作为概念证明,我已经基于these instructions 的修改版本建立了一个基本的远程服务器/客户端解决方案。

我还找到了this article,它描述了如何将基于 APM 的异步操作转换为基于 TPL 的异步任务(使用 Task.Factory.FromAsync

我不确定的是我是否必须在.BeginInvoke() 中指定回调函数并指定.EndInvoke()。如果两者都需要,回调函数和.EndInvoke()之间究竟有什么区别。如果只需要一个,我应该使用哪一个来返回值并确保我have no memory leaks。

这是我当前的代码,我没有将回调传递给.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);
    

【问题讨论】:

【参考方案1】:

回调函数和.EndInvoke() 之间的区别在于回调将在线程池中的任意线程上执行。如果您必须确保从调用 BeginInvoke 的同一线程上的读取中获得结果,则不应使用回调,而是轮询 IAsyncResult 对象并在操作完成时调用.EndInvoke()

如果您在.Beginnvoke() 之后立即调用.EndInvoke(),则会阻塞线程直到操作完成。这会起作用,但扩展性会很差。

所以,你做的似乎没问题!

【讨论】:

【参考方案2】:

出于效率原因,您必须指定回调函数。如果 FromAsync 只有一个 IAsyncResult 可以使用,则在异步结果完成时无法通知它。它将不得不使用一个事件来等待。这会阻塞一个线程(或者,它注册一个线程池等待可能还不错)。

高效的异步 IO 需要一定程度的回调。

如果您要异步,我假设您这样做是因为您有许多并发调用或调用运行时间很长。因此,您应该使用更有效的机制。

如果您没有很多或长时间运行的调用,那么异步不会以任何方式帮助您提高性能,但它仍可能使 GUI 编程更容易。

所以这是不正确的:

public async Task<double> RemoteTimeConsumingRemoteCall()

    var timeConsumingCallDelegate = new TimeConsumingCallDelegate(service.TimeConsumingRemoteCall);

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

为了确保您的实现确实不会阻塞任何线程,我会这样做:在服务器端插入 Thread.Sleep(100000) 并在客户端发出 1000 个并发调用。你应该会发现线程数没有增加。

【讨论】:

以上是关于在 TPL 任务中包装 .NET Remoting 异步方法的主要内容,如果未能解决你的问题,请参考以下文章

TPL异步并行编程之任务超时

三分钟总览微软任务并行库TPL

Wildfly 10.1.0 Final Remoting端点任务线程不断增长

在 TPL 中快速抛出未处理的异常

浅谈.Net异步编程的前世今生----TPL篇

如何访问由 C# 中包装的非.net API 返回的数组?