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

时间:2014-03-21 17:10:55

标签: c# multithreading performance wcf asynchronous

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

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

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

我的问题是:

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

  2. 当我没有为相应的BeginXXX进行EndXXX调用时,有人会介意向我解释究竟发生了什么吗?在Visual Studio中的调试器下,我似乎能够看到通过I / O完成端口在.NET框架中处理另一个答案,并且此处理不是来自我的客户端调用EndXXX。而且由于没有进行EndXXX调用,我似乎没有任何内存泄漏。我假设所有涉及的对象都是垃圾收集。

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

  4. 恕我直言,同步XXX方法实现将始终返回一个答案 需要在某处处理。它是否发生在客户端或服务器上 在我的情况下,当我无法拨打EndXXX时?

  5. 使用WaitHandles是一种等待最快结果的好方法吗?

  6. 如果我必须为每个BeginXXX调用EndXXX,我发送的东西很尴尬。我将不得不将无趣的EndXXX调用委托给另一个忽略结果的线程。在原始线程中调用所有EndXXX调用将无法以同步方式获取并使用更快的答案。

3 个答案:

答案 0 :(得分:5)

  1. 文档说你必须调用end方法。如果你违反了文档的要求,你就处于未定义的行为领域。资源可能泄漏。也许他们只是在负荷下这样做,谁知道?
  2. 我不知道,抱歉。我给出了部分答案。我的建议:实现一个什么都不做的服务方法,并在一个循环中调用它10M次。资源泄漏了吗?如果是的话,你有答案。
  3. 不,服务器和客户端是独立的。服务器可以是同步的,客户端可以是异步的,反之亦然。两者都无法区分对方的不同之处。这两个服务由TCP和明确定义的协议分隔。客户端甚至不可能知道服务器的功能。服务器甚至不必使用.NET。
  4. 我不确定你在问什么。在引擎盖下,WCF客户端使用TCP。传入的数据将被处理"某处" (实际上在线程池上)。
  5. 如果您的代码基本上是同步的,那么这是您可以做的最好的。您将刻录一个等待N个异步服务调用的线程。没关系。
  6. 为什么不在BeginXXX中指定一个除了致电EndXXX之外什么都不做的回调?这样你总是打电话给EndXXX并且符合框架的使用方式。你仍然可以使用等待句柄。

答案 1 :(得分:2)

取决于您调用开始/结束模式的对象。有些人知道泄漏。来自CLR通过C#by Jeffrey Richter:

  

您必须致电Endxxx,否则您将泄漏资源。 CLR分配了一些   启动异步操作时的内部资源。如果是Endxxx   永远不会被调用,这些资源只会在被收回时被收回   流程终止。

答案 2 :(得分:1)

  

AFAIK基于任务的模式使用线程池来处理其工作。   我的客户每秒拨打数千个电话,完全是   垃圾线程池。

如果你使用了Task.RunTask.Factory.StartNew,那就是这样。 Task.Factory.FromAsync本身并没有明确地创建或切换线程。

回到你的场景:

  

我只想使用回复更快的服务回复,   即,当WaitHandle.WaitAny以索引返回时,我只调用EndXXX   使用相应的IAsyncResult来获得更快的结果。我没有   永远打电话给另一个EndXXX。

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

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);
    }
}

实现无所谓服务 - 答案 - 更快逻辑:

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(输入/输出完成端口)线程。有关详细信息,请查看Stephen Cleary的"There Is No Thread"。完成回调将自动调用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应用程序的情况下,它可能正在服务另一个传入的客户端请求。

理想情况下,您的逻辑应该是&#34;始终异步&#34;,直到某个根入口点。例如,使用控制台应用程序:

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);
    }
}