从Async方法关闭WCF服务?

时间:2014-05-20 21:56:44

标签: c# .net wcf asynchronous async-await

我在.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我创建一个拆分函数,然后在每个1000的WhenAll中调用我的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消息,但finally块关闭它,所以第二批1000试图运行抛出异常。如果我删除了finally块,代码就可以正常运行 - 但显然不是关闭这个WCF客户端的好习惯。

我曾尝试将它放在我评估cars.count的if else块之后 - 但是如果用户上传例如2000辆汽车并且完成并且运行时间为1分钟 - 同时由于用户拥有控制权网页他们可以上传另一个2000或另一个用户可以上传,并再次与一个例外。

有没有人能够正确关闭外部服务客户端的好方法?

1 个答案:

答案 0 :(得分:3)

基于你的the related question,你的“分裂”逻辑似乎没有给你你想要实现的目标。 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();
    }
}

根据评论更新

  外部WebService公司可以接受我最多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(); }

GetAllTokenIdForCarsAsync

现在您可以将所有7000辆车传递到{{1}}。这是一个骨架,如果任何批处理请求失败,可以通过一些重试逻辑进行改进(我将其留给您)。