从异步方法关闭WCF服务?方法、WCF

2023-09-05 23:49:08 作者:拥你入心i

我有我创建的.NET 4.5.2其中要求到外部第三方WCF服务异步获取信息的MVC 5 ASP.NET应用服务层项目。调用外部服务的原始方法是如下(有3个在总的这些所有类似我称之为为了从我的GetInfoFromExternalService方法(注意它不是实际调用了 - 只需将其命名为图)

I have a service layer project on an MVC 5 ASP.NET application I am creating on .NET 4.5.2 which calls out to an External 3rd Party WCF Service to Get Information asynchronously. An original method to call external service was as below (there are 3 of these all similar in total which I call in order from my GetInfoFromExternalService method (note it isnt actually called that - just naming it for illustration)

    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客户端被关闭,并设置为空,然后newed了其他请求时作出。直到需要改变将由此作出,如果汽车由用户通过在数超过1000个I创建分割功能,然后叫我GetInfoFromExternalService方法在WhenAll与每千这是工作的罚款 - 如下:

So that meant that when each async call had completed the finally block ran - the WCF client was closed and set to null and then newed up when another request was made. This was working fine until a change needed to be made whereby if the number of cars passed in by User exceeds 1000 I create a Split Function and then call my GetInfoFromExternalService method in a WhenAll with each 1000 - as below:

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服务,但finally块关闭它,因此第二批1000试图运行抛出一个异常。如果我删除finally块中的code工作正常 - 但它显然不是很好的做法,不能关闭此WCF客户端

However this now falls over as if I have 3000 cars the method call to GetTokenId news up the WCF service but the finally blocks closes it so the second batch of 1000 that is attempting to be run throws an exception. If I remove the finally block the code works ok - but it is obviously not good practice to not be closing this WCF client.

我曾试图把它后,我如果其中cars.count评估else块 - 但是,如果用户上传的如2000汽车和完成并运行在说1分钟 - 在此期间,当用户在有控制网页他们可以上传其他2000或其他用户可以上传并再次翻倒有一个例外。

I had tried putting it after my if else block where the cars.count is evaluated - but if a User uploads for e.g 2000 cars and that completes and runs in say 1 min - in the meantime as the user had control in the Webpage they could upload another 2000 or another User could upload and again it falls over with an Exception.

有没有一个很好的方法任何人都可以看到正确关闭外部服务客户?

Is there a good way anyone can see to correctly close the External Service Client?

推荐答案

根据的你的相关问题,你分裂的逻辑似乎不给你你想要达到的目的。 WhenAll 仍然执行并行请求,所以你最终可能会运行超过1000个请求在任何给定时间的时刻。使用 SemaphoreSlim 来扼杀同时激活请求的数量和限制数量为1000。这样一来,你不需要做任何分裂。

Based on the related question of yours, your "split" logic doesn't seem to give you what you're trying to achieve. WhenAll still executes requests in parallel, so you may end up running more than 1000 requests at any given moment of time. Use SemaphoreSlim to throttle the number of simultaneously active requests and limit that number to 1000. This way, you don't need to do any splits.

另一个问题可能是你如何处理创建/处置 ExternalServiceClient 客户端。我认为会有一个竞争条件存在。

Another issue might be in how you handle the creation/disposal of ExternalServiceClient client. I suspect there might a race condition there.

最后,当您重新遥,从块,你至少应该包括参照原有的异常。

Lastly, when you re-throw from the catch block, you should at least include a reference to the original exception.

下面是如何解决这些问题(未经测试,但应该给你的想法):

Here's how to address these issues (untested, but should give you the idea):

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

更新基于注释:

外部WebService的公司可以接受我传球达5000车   在一个调用的对象 - 尽管他们建议分裂成批次   1000和运行多达5个并行在同一时间 - 所以,当我提到7000    - 我不意味GetTokenIdForCarAsync将被称为7000次 - 我的code现在应该叫7倍 - 即给我回7   令牌IDS - 我想知道我可以用你的信号灯苗条先运行   5并联,然后2

the External WebService company can accept me passing up to 5000 car objects in one call - though they recommend splitting into batches of 1000 and run up to 5 in parallel at one time - so when I mention 7000 - I dont mean GetTokenIdForCarAsync would be called 7000 times - with my code currently it should be called 7 times - i.e giving me back 7 token ids - I am wondering can I use your semaphore slim to run first 5 in parallel and then 2

的变化是最小的(但未经测试)。第一:

The changes are minimal (but untested). First:

const int MAX_PARALLEL = 5;

然后,使用马克·Gravell的 ChunkExtension.Chunkify ,我们引进 GetAllTokenIdForCarsAsync ,这反过来将调用 GetTokenIdForCarsAsync 从上面的:

Then, using Marc Gravell's ChunkExtension.Chunkify, we introduce GetAllTokenIdForCarsAsync, which in turn will be calling GetTokenIdForCarsAsync from above:

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 。这是一个骨架,它可以用一些重试逻辑改善如果任意一批请求已失败(我要走了由你)。

Now you can pass all 7000 cars into GetAllTokenIdForCarsAsync. This is a skeleton, it can be improved with some retry logic if any of the batch requests has failed (I'm leaving that up to you).