我有2个服务为WCF调用服务。从客户端我发送相同的异步WCF BeginXXX调用到两个服务,然后开始等待WaitHandle.WaitAny(waitHandles)的回复,其中waitHandles是来自2个BeginXXX调用返回的IAsyncResults的WaitHandles数组。
我想只使用来自服务的回复更快,即当WaitHandle.WaitAny返回索引时,我只使用相应的IAsyncResult调用EndXXX以获得更快的结果。我永远不会打电话给另一个EndXXX。
我这样做的原因是有时服务在垃圾收集中使用几秒钟而且无法快速回答。根据我的经验,这两个服务通常在不同的时间进行垃圾收集,因此其中一个几乎总是能够返回快速答案。我的客户端应用程序非常关键,我需要在几毫秒内得到答案。
我的问题是:
我可以安全地忽略对其他较慢回答的服务调用EndXXX方法吗?我对较慢的结果不感兴趣,但希望尽快使用更快的结果。根据我的实验,即使我没有为相应较慢的BeginXXX异步结果调用EndXXX方法,也没有什么不好的事情发生。
当我没有为相应的BeginXXX进行EndXXX调用时,有人会介意向我解释究竟发生了什么吗?在Visual Studio中的调试器下,我似乎能够看到通过I / O完成端口在.NET框架中处理另一个答案,并且此处理不是来自我的客户端调用EndXXX。而且由于没有进行EndXXX调用,我似乎没有任何内存泄漏。我假设所有涉及的对象都是垃圾收集。
服务器端方法XXX实现是单个同步XXX还是显式异步BeginXXX / EndXXX对是否有任何区别?
恕我直言,同步XXX方法实现将始终返回一个答案 需要在某处处理。它是否发生在客户端或服务器上 在我的情况下,当我无法拨打EndXXX时?
使用WaitHandles是一种等待最快结果的好方法吗?
如果我必须为每个BeginXXX调用EndXXX,我发送的东西很尴尬。我将不得不将无趣的EndXXX调用委托给另一个忽略结果的线程。在原始线程中调用所有EndXXX调用将无法以同步方式获取并使用更快的答案。
答案 0 :(得分:5)
BeginXXX
中指定一个除了致电EndXXX
之外什么都不做的回调?这样你总是打电话给EndXXX
并且符合框架的使用方式。你仍然可以使用等待句柄。答案 1 :(得分:2)
取决于您调用开始/结束模式的对象。有些人知道泄漏。来自CLR通过C#by Jeffrey Richter:
您必须致电Endxxx,否则您将泄漏资源。 CLR分配了一些 启动异步操作时的内部资源。如果是Endxxx 永远不会被调用,这些资源只会在被收回时被收回 流程终止。
答案 2 :(得分:1)
AFAIK基于任务的模式使用线程池来处理其工作。 我的客户每秒拨打数千个电话,完全是 垃圾线程池。
如果你使用了Task.Run
或Task.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);
}
}