我可以忽略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打电话。
我这样做的原因是,有时服务在垃圾收集中使用几秒钟,并且无法快速响应。根据我的经验,这两个服务通常在不同的时间进行垃圾收集,所以其中一个几乎总是能够返回一个快速的答案。我的客户端应用程序非常着急,我需要在几毫秒内得到答案。
我的问题是:
-
对于应答较慢的其他服务,我可以安全地忽略调用EndXXX方法吗?我对较慢的结果不感兴趣,但想尽快使用更快的结果。根据我的实验,即使我不为相应的较慢的BeginXXX异步结果调用EndXXX方法,似乎也没有什么不好的事情发生。
-
有人介意向我解释一下,当我不为相应的BeginXXX调用EndXXX时,究竟会发生什么?在Visual Studio的调试器下,我似乎能够看到另一个答案是通过I/O完成端口在。net框架中处理的,并且这个处理不是来自我的客户端调用EndXXX。我似乎没有任何内存泄漏,因为没有进行EndXXX调用。
-
服务器端方法XXX实现是单个同步XXX还是显式异步BeginXXX/EndXXX对有区别吗?
-
IMHO一个同步的XXX方法实现将总是返回一个答案需要找个地方处理。它发生在客户端还是服务器上在我的情况下,当我不能调用EndXXX?
-
使用WaitHandles是等待最快结果的良好且最有效的方式吗?
-
如果我必须为每个BeginXXX调用EndXXX,我已经发送出去了,这让事情变得相当尴尬。我必须将无趣的EndXXX调用委托给另一个线程,该线程只会忽略结果。在我的原始线程中调用所有EndXXX调用将破坏以同步方式获取和使用更快的答案的目的。
- 文档说你必须调用end方法。如果你违反了文档的要求,你就处于未定义行为区。资源可能会泄漏。也许他们只是在负荷下这样做,谁知道呢? 我不知道,对不起。我只给出了部分答案。我的建议是:实现一个什么都不做的服务方法,并在循环中调用它1000万次。资源是否泄漏?
- 否,服务器和客户端是独立的。服务器可以是同步的,客户端可以是异步的,反之亦然。两者甚至不能分辨出对方在做什么。这两个服务由TCP和一个定义良好的协议分开。客户机甚至不可能知道服务器在做什么。服务器甚至不必使用。net。 我不知道你在问什么。在底层,WCF客户机使用TCP。传入数据将在"某处"处理(实际上是在线程池上)。如果你的代码基本上是同步的,这是你能做的最好的。您将在等待N个异步服务调用时消耗一个线程。没关系。
- 为什么不在
BeginXXX
中指定一个回调,除了调用EndXXX
之外什么都不做?这样你总是调用EndXXX
并符合框架的使用方式。你仍然可以使用等待句柄。
取决于调用开始/结束模式的对象。有些是众所周知的泄漏。从CLR到c#:
必须调用Endxxx,否则会泄漏资源。CLR分配一些初始化异步操作时的内部资源。如果Endxxx的时候,这些资源才会被回收过程终止。
基于任务的模式使用线程池来处理它的工作。我的客户每秒钟打几千个电话,而且会完全删除线程池
如果您使用Task.Run
或Task.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);
}
}