c# - 从异步方法关闭 WCF 服务?

标签 c# .net wcf asynchronous async-await

我在 .NET 4.5.2 上创建的 MVC 5 ASP.NET 应用程序上有一个服务层项目,该项目调用外部第 3 方 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 block 就会运行 - 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 服务更新为消息,但 finally block 将其关闭,因此尝试运行的第二批 1000 辆车会抛出异常。如果我删除 finally block ,代码可以正常工作 - 但不关闭此 WCF 客户端显然不是一个好习惯。

我曾尝试将其放在评估 cars.count 的 if else block 之后 - 但如果用户上传了例如 2000 辆汽车,并且在 1 分钟内完成并运行 - 同时用户可以控制他们可以上传另外 2000 个网页,或者其他用户可以上传,但它会再次因异常而失败。

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

最佳答案

基于the related question对于你来说,你的“ split ”逻辑似乎并没有给你想要实现的目标。 WhenAll 仍然并行执行请求,因此您最终可能会在任何给定时间运行超过 1000 个请求。使用 SemaphoreSlim 来限制同时事件请求的数量,并将该数量限制为 1000。这样,您就不需要进行任何拆分。

另一个问题可能是如何处理 ExternalServiceClient 客户端的创建/处置。我怀疑那里可能存在竞争条件。

最后,当您从 catch block 重新抛出时,您至少应该包含对原始异常的引用。

以下是解决这些问题的方法(未经测试,但应该可以为您提供想法):

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

根据评论更新:

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

变化很小(但未经测试)。第一:

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 中。这是一个框架,如果任何批处理请求失败,可以通过一些重试逻辑对其进行改进(我将其留给您)。

关于c# - 从异步方法关闭 WCF 服务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23770657/

相关文章:

c# - 为什么 c# 不保留匿名委托(delegate)调用的上下文?

c# - 如何区分致命和非致命的 .NET 异常

wcf - 如何使用命令行工具将 .net 4 安装到 IIS

wcf - 有没有办法同时使用 WCF 和 .NET Remoting?

c# - 由于表达式树不可序列化,如何使用无法使用 WCF 工作的表达式树?

c# - 在 WPF 的代码隐藏中为 ListBox 创建 ItemTemplate

c# - 尝试使用 C# 将日期(不带时间)插入 .DBF(Visual Fox Pro 数据库)

c# - 如何停止 Page.DataBind() 的递归调用?

c# - 如何为动态生成标题文本的数据网格标题添加工具提示?

.net - msbuild 未在 teamcity 中创建工件