防止WCF客户端发出太多请求

时间:2018-06-21 02:49:10

标签: web-services wcf cqrs wcf-client throttling

我正在编写一个客户端向Web服务(CQRS)发出命令的应用程序

  • 客户端是用C#编写的
  • 客户端使用WCF代理发送消息
  • 客户端使用异步模式来调用Web服务
  • 客户端可以一次发出多个请求。

我的问题是,有时客户端只是发出太多请求,服务开始返回它太忙了。

这里是一个例子。我正在注册订单,订单数量可能从几千到几千。

var taskList = Orders.Select(order => _cmdSvc.ExecuteAsync(order))
                     .ToList();

await Task.WhenAll(taskList);

基本上,我为每个订单调用ExecuteAsync并取回一个Task。然后我只等他们完成。

我真的不想修复此服务器端,因为无论我对其进行多大调整,客户端仍然可以通过发送10,000个请求来杀死它。

所以我的问题是。我是否可以以任何方式配置WCF客户端,使其仅接收所有请求并发送最多20个请求,一旦完成,它就会自动分派下一个,依此类推?还是我返回的Task链接到实际的HTTP请求,因此在请求实际分派之前无法返回?

如果是这种情况,而WCF Client根本无法做到这一点,那么我的想法是用一个将命令排队的类来装饰WCF Client,返回一个Task(使用TaskCompletionSource),然后确保不再存在而不是说一次有20个请求处于活动状态。我知道这可以解决问题,但是我想问问是否有人知道做类似这样的事情的库或类吗?

这有点像节流,但我不想这样做,因为我不想限制在给定时间内可以发送多少个请求,而是在任何给定时间内可以存在多少个活动请求时间。

1 个答案:

答案 0 :(得分:0)

基于@PanagiotisKanavos建议,这是我如何解决此问题的方法。

RequestLimitCommandService充当实际服务的装饰器,并以innerSvc的形式传递给构造器。一旦有人调用ExecuteAsync,就会创建一个完成源,并将其与命令一起发布到ActonBlock,然后,调用者会从完成源中获取任务。

ActionBlock随后将调用处理方法。此方法将命令发送到Web服务。根据发生的情况,此方法将使用完成源来通知原始发送方命令已成功处理,或将发生的异常附加到源。

public class RequestLimitCommandService : IAsyncCommandService
{
    private class ExecutionToken
    {
        public TaskCompletionSource<bool> Source { get; }
        public ICommand Command { get; }

        public ExecutionToken(TaskCompletionSource<bool> source, ICommand command)
        {
            Source = source;
            Command = command;
        }
    }

    private IAsyncCommandService _innerSrc;
    private ActionBlock<ExecutionToken> _block;

    public RequestLimitCommandService(IAsyncCommandService innerSvc, int maxDegreeOfParallelism)
    {
        _innerSrc = innerSvc;
        var options = new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism };
        _block = new ActionBlock<ExecutionToken>(Execute, options);
    }

    public Task IAsyncCommandService.ExecuteAsync(ICommand command)
    {
        var source = new TaskCompletionSource<bool>();
        var token = new ExecutionToken(source, command);
        _block.Post(token);
        return source.Task;
    }

    private async Task Execute(ExecutionToken token)
    {
        try
        {
            await _innerSrc.ExecuteAsync(token.Command);
            token.Source.SetResult(true);
        }
        catch (Exception ex)
        {
            token.Source.SetException(ex);
        }
    }   
}