我可以忽略WCF异步EndXXX调用吗?

本文关键字:调用 EndXXX 异步 WCF 我可以 | 更新日期: 2023-09-27 17:51:18

我有2个服务WCF调用。从客户端,我发送相同的异步WCF BeginXXX调用到两个服务,然后开始等待waitthandle . waitany (waitHandles)的回复,其中waitthandles是由两个BeginXXX调用返回的IAsyncResults的waitthandle数组。

我想只使用响应更快的服务的响应,即当WaitHandle时。WaitAny返回一个索引,我只调用EndXXX与相应的IAsyncResult,以获得更快的结果。我从来不给另一个EndXXX打电话。

我这样做的原因是,有时服务在垃圾收集中使用几秒钟,并且无法快速响应。根据我的经验,这两个服务通常在不同的时间进行垃圾收集,所以其中一个几乎总是能够返回一个快速的答案。我的客户端应用程序非常着急,我需要在几毫秒内得到答案。

我的问题是:

  1. 对于应答较慢的其他服务,我可以安全地忽略调用EndXXX方法吗?我对较慢的结果不感兴趣,但想尽快使用更快的结果。根据我的实验,即使我不为相应的较慢的BeginXXX异步结果调用EndXXX方法,似乎也没有什么不好的事情发生。

  2. 有人介意向我解释一下,当我不为相应的BeginXXX调用EndXXX时,究竟会发生什么?在Visual Studio的调试器下,我似乎能够看到另一个答案是通过I/O完成端口在。net框架中处理的,并且这个处理不是来自我的客户端调用EndXXX。我似乎没有任何内存泄漏,因为没有进行EndXXX调用。

  3. 服务器端方法XXX实现是单个同步XXX还是显式异步BeginXXX/EndXXX对有区别吗?

  4. IMHO一个同步的XXX方法实现将总是返回一个答案需要找个地方处理。它发生在客户端还是服务器上在我的情况下,当我不能调用EndXXX?

  5. 使用WaitHandles是等待最快结果的良好且最有效的方式吗?

  6. 如果我必须为每个BeginXXX调用EndXXX,我已经发送出去了,这让事情变得相当尴尬。我必须将无趣的EndXXX调用委托给另一个线程,该线程只会忽略结果。在我的原始线程中调用所有EndXXX调用将破坏以同步方式获取和使用更快的答案的目的。

我可以忽略WCF异步EndXXX调用吗?

  1. 文档说你必须调用end方法。如果你违反了文档的要求,你就处于未定义行为区。资源可能会泄漏。也许他们只是在负荷下这样做,谁知道呢?
  2. 我不知道,对不起。我只给出了部分答案。我的建议是:实现一个什么都不做的服务方法,并在循环中调用它1000万次。资源是否泄漏?
  3. 否,服务器和客户端是独立的。服务器可以是同步的,客户端可以是异步的,反之亦然。两者甚至不能分辨出对方在做什么。这两个服务由TCP和一个定义良好的协议分开。客户机甚至不可能知道服务器在做什么。服务器甚至不必使用。net。
  4. 我不知道你在问什么。在底层,WCF客户机使用TCP。传入数据将在"某处"处理(实际上是在线程池上)。如果你的代码基本上是同步的,这是你能做的最好的。您将在等待N个异步服务调用时消耗一个线程。没关系。
  5. 为什么不在BeginXXX中指定一个回调,除了调用EndXXX之外什么都不做?这样你总是调用EndXXX并符合框架的使用方式。你仍然可以使用等待句柄。

取决于调用开始/结束模式的对象。有些是众所周知的泄漏。从CLR到c#:

必须调用Endxxx,否则会泄漏资源。CLR分配一些初始化异步操作时的内部资源。如果Endxxx的时候,这些资源才会被回收过程终止。

基于任务的模式使用线程池来处理它的工作。我的客户每秒钟打几千个电话,而且会完全删除线程池

如果您使用Task.RunTask.Factory.StartNew,则会如此。Task.Factory.FromAsync本身并不显式地创建或切换线程。

回到你的场景:

我只想使用响应速度更快的服务的响应,即,当WaitHandle时。WaitAny返回一个索引,我只调用EndXXX使用相应的IAsyncResult来获得更快的结果。我不永远不要调用另一个EndXXX.

让我们为BeginXXX/EndXXX异步服务调用创建Task包装器:

public static class WcfExt
{
    public static Task<object> WorkAsync(this IService service, object arg)
    {
        return Task.Factory.FromAsync(
             (asyncCallback, asyncState) =>
                 service.BeginWork(arg, asyncCallback, asyncState),
             (asyncResult) =>
                 service.EndWork(asyncResult), null);
    }
}

并实现whatever-service-answers-faster逻辑:

static async Task<object> CallBothServicesAsync(
    IService service1, IService service2, object arg)
{
    var task1 = service1.WorkAsync(arg);
    var task2 = service2.WorkAsync(arg);
    var resultTask = await Task.WhenAny(task1, task2).ConfigureAwait(false);
    return resultTask.Result;
}
到目前为止,还没有阻塞代码,我们仍然没有显式地创建新线程。WorkAsync包装器将一个延续回调传递给BeginWork。当BeginWork启动的操作完成时,服务将调用此回调。

它将在上被调用,无论线程恰好服务于该操作的完成。大多数情况下,这是线程池中的随机IOCP(输入/输出完成端口)线程。欲了解更多细节,请查看斯蒂芬·克利里的《没有线索》。完成回调将自动调用EndWork 来完成操作并检索其结果,因此服务不会泄漏资源,并将结果存储在Task<object>实例中(由WorkAsync返回)。

然后,await Task.WhenAny之后的代码将继续在该特定线程上执行。所以,可能在await 之后有一个线程切换,但是它自然地使用异步操作已经完成的IOCP线程。

你几乎不需要使用低级同步原语,比如任务并行库中的手动重置事件。例如,如果你需要等待CallBothServicesAsync的结果,你只需这样做:

var result = CallBothServicesAsync(service1, service2).Result;
Console.WriteLine("Result: " + result);

等于:

var task = CallBothServicesAsync(service1, service2);
task.Wait();
Console.WriteLine("Result: " + task.result);

此代码将阻塞当前线程,类似于原始场景中的WaitHandle.WaitAny

现在,像这样的阻塞也不推荐,因为你会失去异步编程模型的优势,损害应用程序的可扩展性。阻塞的线程可以做一些其他有用的工作,而不是等待,例如,在一个web应用程序的情况下,它可以服务另一个传入的客户端请求。

理想情况下,你的逻辑应该是"异步所有的方式",直到一些根入口点。例如,对于控制台应用程序:

static async Task CoreLoopAsync(CancellationToken token)
{
    using(var service1 = CreateWcfClientProxy())
    using(var service2 = CreateWcfClientProxy())
    {
        while (true)
        {
            token.ThrowIfCancellationRequested();
            var result = await CallBothServicesAsync("data");
            Console.WriteLine("Result: " + result);
        }
    }
}
static void Main()
{
    var cts = CancellationTokenSource(10000); // cancel in 10s
    try
    {
        // block at the "root" level, i.e. inside Main
        CoreLoopAsync(cts.Token).Wait();
    }
    catch (Exception ex)
    {
        while (ex is AggregatedException)
            ex = ex.InnerException;
        // report the error
        Console.WriteLine(ex.Message);
    }
}