[英]Closing WCF Service from Async method?
我在.NET 4.5.2上創建的MVC 5 ASP.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;
}
}
因此,這意味着當每個異步調用完成后,將運行finally塊-WCF客戶端關閉並設置為null,然后在發出另一個請求時進行更新。 這一直很好,直到需要進行更改為止,如果用戶傳遞的汽車數量超過1000,我將創建一個Split函數,然后在WhenAll中分別使用1000調用我的GetInfoFromExternalService方法,如下所示:
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輛車,在WCF服務上調用GetTokenId新聞的方法調用,但是最后一個塊將其關閉,因此試圖運行的第二批1000拋出異常。 如果我刪除了finally塊,則代碼可以正常工作-但是,不關閉此WCF客戶端顯然不是一個好習慣。
我曾嘗試將其放在if if塊后面,評估cars.count的位置-但是,如果用戶上傳了例如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();
}
}
根據評論更新 :
外部Web服務公司可以接受我在一次調用中傳遞多達5000個汽車對象-盡管他們建議將批量分為1000個並一次並行運行5個對象-因此,當我提到7000時,我並不是說GetTokenIdForCarAsync將被稱為7000次-當前使用我的代碼應該調用7次-即給我7個令牌ID-我想知道我是否可以使用您的信號量苗條先並行運行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
。 這是一個框架,如果任何批處理請求失敗(可以由您自己決定),可以使用一些重試邏輯進行改進。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.