从异步方法关闭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辆汽车,或者另一个用户可以上传,并且再次出现异常。

是否有一个好方法,任何人都可以看到正确关闭外部服务客户端?

从异步方法关闭WCF服务

根据你的相关问题,你的"分裂"逻辑似乎并没有给你你想要达到的目标。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。这是一个框架,如果任何批处理请求失败(我把它留给你),可以使用一些重试逻辑来改进它。