从异步方法关闭WCF服务
本文关键字:WCF 服务 异步方法 | 更新日期: 2023-09-27 18:04:14
我有一个MVC 5 ASP的服务层项目。我在。NET 4.5.2上创建的。NET应用程序调用外部第三方WCF服务来异步获取信息。调用外部服务的原始方法如下(总共有3个类似的方法,我从GetInfoFromExternalService方法中按顺序调用它们(注意它实际上不是这样调用的——只是为了说明而命名)
private async Task<string> GetTokenIdForCarsAsync(Car[] cars)
{
try
{
if (_externalpServiceClient == null)
{
_externalpServiceClient = new ExternalServiceClient("WSHttpBinding_IExternalService");
}
string tokenId= await _externalpServiceClient .GetInfoForCarsAsync(cars).ConfigureAwait(false);
return tokenId;
}
catch (Exception ex)
{
//TODO plug in log 4 net
throw new Exception("Failed" + ex.Message);
}
finally
{
CloseExternalServiceClient(_externalpServiceClient);
_externalpServiceClient= null;
}
}
所以这意味着当每个异步调用完成最后一个块运行时- WCF客户端被关闭并设置为null,然后在发出另一个请求时重新启动。这工作得很好,直到需要进行更改,即如果用户传入的汽车数量超过1000,我创建一个Split函数,然后在WhenAll中调用我的GetInfoFromExternalService方法,每1000个,如下所示:
if (cars.Count > 1000)
{
const int packageSize = 1000;
var packages = SplitCarss(cars, packageSize);
//kick off the number of split packages we got above in Parallel and await until they all complete
await Task.WhenAll(packages.Select(GetInfoFromExternalService));
}
然而,现在如果我有3000辆车,方法调用GetTokenId新闻WCF服务,但最后阻塞关闭它,所以第二批1000辆试图运行抛出异常。如果我删除了finally块,代码工作正常-但不关闭这个WCF客户端显然不是好的做法。
我曾试着把它放在我的if else块后面,那里有汽车。计数被评估-但是如果一个用户上传2000辆汽车,并且在1分钟内完成并运行-在此期间,由于用户在网页中拥有控制权,他们可以上传另外2000辆汽车,或者另一个用户可以上传,并且再次出现异常。
是否有一个好方法,任何人都可以看到正确关闭外部服务客户端?
根据你的相关问题,你的"分裂"逻辑似乎并没有给你你想要达到的目标。WhenAll
仍然并行执行请求,因此您最终可能在任何给定时刻运行超过1000个请求。使用SemaphoreSlim
限制同时活动请求的数量,并将该数量限制为1000。这样,你就不需要做任何分割了。
另一个问题可能是如何处理ExternalServiceClient
客户端的创建/处置。我怀疑这里可能存在竞争条件
最后,当您从catch
块中重新抛出时,您至少应该包含对原始异常的引用。
下面是如何解决这些问题(未经测试,但应该给你一个想法):
const int MAX_PARALLEL = 1000;
SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(MAX_PARALLEL);
volatile int _activeClients = 0;
readonly object _lock = new Object();
ExternalServiceClient _externalpServiceClient = null;
ExternalServiceClient GetClient()
{
lock (_lock)
{
if (_activeClients == 0)
_externalpServiceClient = new ExternalServiceClient("WSHttpBinding_IExternalService");
_activeClients++;
return _externalpServiceClient;
}
}
void ReleaseClient()
{
lock (_lock)
{
_activeClients--;
if (_activeClients == 0)
{
_externalpServiceClient.Close();
_externalpServiceClient = null;
}
}
}
private async Task<string> GetTokenIdForCarsAsync(Car[] cars)
{
var client = GetClient();
try
{
await _semaphoreSlim.WaitAsync().ConfigureAwait(false);
try
{
string tokenId = await client.GetInfoForCarsAsync(cars).ConfigureAwait(false);
return tokenId;
}
catch (Exception ex)
{
//TODO plug in log 4 net
throw new Exception("Failed" + ex.Message, ex);
}
finally
{
_semaphoreSlim.Release();
}
}
finally
{
ReleaseClient();
}
}
根据注释更新
External WebService公司可以接受我传递多达5000辆车对象在一次调用中调用——尽管他们建议将对象分成若干批1000个,同时并行运行5个,所以当我提到7000的时候-我不是说GetTokenIdForCarAsync会被调用7000次-用我目前的代码它应该被调用7次-也就是给我返回7令牌id -我想知道我可以使用你的信号量slim首先运行5并行,然后2
更改很小(但未经测试)。第一:
const int MAX_PARALLEL = 5;
然后,使用Marc Gravell的ChunkExtension.Chunkify
,我们引入GetAllTokenIdForCarsAsync
,它反过来将从上面调用GetTokenIdForCarsAsync
:
private async Task<string[]> GetAllTokenIdForCarsAsync(Car[] cars)
{
var results = new List<string>();
var chunks = cars.Chunkify(1000);
var tasks = chunks.Select(chunk => GetTokenIdForCarsAsync(chunk)).ToArray();
await Task.WhenAll(tasks);
return tasks.Select(task => task.Result).ToArray();
}
现在你可以通过所有7000辆车进入GetAllTokenIdForCarsAsync
。这是一个框架,如果任何批处理请求失败(我把它留给你),可以使用一些重试逻辑来改进它。