Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/316.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 具有TPL数据流的请求/响应模式_C#_.net_Task Parallel Library_Tpl Dataflow - Fatal编程技术网

C# 具有TPL数据流的请求/响应模式

C# 具有TPL数据流的请求/响应模式,c#,.net,task-parallel-library,tpl-dataflow,C#,.net,Task Parallel Library,Tpl Dataflow,在使用TPL数据流库时,我们遇到了一个需要请求/响应模式的问题。我们的问题是我们有一个.NET核心API来调用依赖服务。依赖服务限制并发请求。我们的API不限制并发请求;因此,我们一次可以收到数千个请求。在这种情况下,依赖服务将在达到其限制后拒绝请求。因此,我们实现了一个BufferBlock和一个TransformBlock。表演很扎实,效果很好。我们测试了我们的API前端,1000个用户每秒发出100个请求,0个问题。缓冲块缓冲请求,转换块并行执行所需数量的请求。依赖服务接收我们的请求并作出

在使用TPL数据流库时,我们遇到了一个需要请求/响应模式的问题。我们的问题是我们有一个.NET核心API来调用依赖服务。依赖服务限制并发请求。我们的API不限制并发请求;因此,我们一次可以收到数千个请求。在这种情况下,依赖服务将在达到其限制后拒绝请求。因此,我们实现了一个
BufferBlock
和一个
TransformBlock
。表演很扎实,效果很好。我们测试了我们的API前端,1000个用户每秒发出100个请求,0个问题。缓冲块缓冲请求,转换块并行执行所需数量的请求。依赖服务接收我们的请求并作出响应。我们在转换块操作中返回该响应,一切正常。我们的问题是缓冲块和转换块断开连接,这意味着请求/响应不同步。我们遇到一个问题,请求将收到另一个请求者的响应(请参阅下面的代码)

具体到下面的代码,我们的问题在于
GetContent
方法。该方法是从API中的服务层调用的,该层最终是从控制器调用的。下面的代码和服务层是单例的。缓冲区的
sendaync
与转换块
ReceiveAsync
断开连接,从而返回任意响应,而不一定是发出的请求

因此,我们的问题是:有没有一种方法可以使用数据流块来关联请求/响应?最终的目标是请求进入我们的API,发送到依赖服务,然后返回到客户机。下面是数据流实现的代码

public class HttpClientWrapper : IHttpClientManager
{
    private readonly IConfiguration _configuration;
    private readonly ITokenService _tokenService;
    private HttpClient _client;

    private BufferBlock<string> _bufferBlock;
    private TransformBlock<string, JObject> _actionBlock;

    public HttpClientWrapper(IConfiguration configuration, ITokenService tokenService)
    {
        _configuration = configuration;
        _tokenService = tokenService;

        _bufferBlock = new BufferBlock<string>();

        var executionDataFlowBlockOptions = new ExecutionDataflowBlockOptions
        {
            MaxDegreeOfParallelism = 10
        };

        var dataFlowLinkOptions = new DataflowLinkOptions
        {
            PropagateCompletion = true
        };

        _actionBlock = new TransformBlock<string, JObject>(t => ProcessRequest(t),
            executionDataFlowBlockOptions);

        _bufferBlock.LinkTo(_actionBlock, dataFlowLinkOptions);
    }

    public void Connect()
    {
        _client = new HttpClient();

        _client.DefaultRequestHeaders.Add("x-ms-client-application-name",
            "ourappname");
    }

    public async Task<JObject> GetContent(string request)
    {
        await _bufferBlock.SendAsync(request);

        var result = await _actionBlock.ReceiveAsync();

        return result;
    }

    private async Task<JObject> ProcessRequest(string request)
    {
        if (_client == null)
        {
            Connect();
        }

        try
        {
            var accessToken = await _tokenService.GetTokenAsync(_configuration);

            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post,
                new Uri($"https://{_configuration.Uri}"));

            // add the headers
            httpRequestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
            // add the request body
            httpRequestMessage.Content = new StringContent(request, Encoding.UTF8,
                "application/json");

            var postRequest = await _client.SendAsync(httpRequestMessage);

            var response = await postRequest.Content.ReadAsStringAsync();

            return JsonConvert.DeserializeObject<JObject>(response);
        }
        catch (Exception ex)
        {
            // log error

            return new JObject();
        }
    }
}
公共类HttpClientWrapper:IHttpClientManager
{
专用只读IConfiguration\u配置;
专用只读ITokenService(令牌服务);
私有HttpClient \u客户端;
专用缓冲块_BufferBlock;
私有转换块_actionBlock;
公共HttpClientWrapper(IConfiguration配置,iTokeService令牌服务)
{
_配置=配置;
_令牌服务=令牌服务;
_bufferBlock=新的bufferBlock();
var executionDataFlowBlockOptions=新的executionDataFlowBlockOptions
{
MaxDegreeOfParallelism=10
};
var dataFlowLinkOptions=新的dataFlowLinkOptions
{
完成=真
};
_actionBlock=new TransformBlock(t=>ProcessRequest(t),
executionDataFlowBlockOptions);
_LinkTo(_actionBlock,dataFlowLinkOptions);
}
公共void Connect()
{
_client=新的HttpClient();
_client.DefaultRequestHeaders.Add(“x-ms-client-application-name”,
“公司名称”);
}
公共异步任务GetContent(字符串请求)
{
wait_bufferBlock.SendAsync(请求);
var result=await_actionBlock.ReceiveAsync();
返回结果;
}
专用异步任务ProcessRequest(字符串请求)
{
如果(_client==null)
{
Connect();
}
尝试
{
var accessToken=wait _tokenService.GetTokenAsync(_配置);
var httpRequestMessage=新的httpRequestMessage(HttpMethod.Post,
新Uri($“https://{u configuration.Uri}”);
//添加标题
httpRequestMessage.Headers.Add(“Authorization”、$“Bearer{accessToken}”);
//添加请求主体
httpRequestMessage.Content=新的StringContent(请求,Encoding.UTF8,
“应用程序/json”);
var postRequest=wait_client.sendaync(httpRequestMessage);
var response=await postRequest.Content.ReadAsStringAsync();
返回JsonConvert.DeserializeObject(响应);
}
捕获(例外情况除外)
{
//日志错误
返回新的JObject();
}
}
}

您需要做的是用id标记每个传入项,以便将数据输入与结果输出关联起来。下面是一个如何做到这一点的示例:

namespace ConcurrentFlows.DataflowJobs {
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;

    /// <summary>
    /// A generic interface defining that:
    /// for a specified input type => an awaitable result is produced.
    /// </summary>
    /// <typeparam name="TInput">The type of data to process.</typeparam>
    /// <typeparam name="TOutput">The type of data the consumer expects back.</typeparam>
    public interface IJobManager<TInput, TOutput> {
        Task<TOutput> SubmitRequest(TInput data);
    }

    /// <summary>
    /// A TPL-Dataflow based job manager.
    /// </summary>
    /// <typeparam name="TInput">The type of data to process.</typeparam>
    /// <typeparam name="TOutput">The type of data the consumer expects back.</typeparam>
    public class DataflowJobManager<TInput, TOutput> : IJobManager<TInput, TOutput> {

        /// <summary>
        /// It is anticipated that jobHandler is an injected
        /// singleton instance of a Dataflow based 'calculator', though this implementation
        /// does not depend on it being a singleton.
        /// </summary>
        /// <param name="jobHandler">A singleton Dataflow block through which all jobs are processed.</param>
        public DataflowJobManager(IPropagatorBlock<KeyValuePair<Guid, TInput>, KeyValuePair<Guid, TOutput>> jobHandler) {
            if (jobHandler == null) { throw new ArgumentException("Argument cannot be null.", "jobHandler"); }

            this.JobHandler = JobHandler;
            if (!alreadyLinked) {
                JobHandler.LinkTo(ResultHandler, new DataflowLinkOptions() { PropagateCompletion = true });
                alreadyLinked = true;
            }
        }

        private static bool alreadyLinked = false;            

        /// <summary>
        /// Submits the request to the JobHandler and asynchronously awaits the result.
        /// </summary>
        /// <param name="data">The input data to be processd.</param>
        /// <returns></returns>
        public async Task<TOutput> SubmitRequest(TInput data) {
            var taggedData = TagInputData(data);
            var job = CreateJob(taggedData);
            Jobs.TryAdd(job.Key, job.Value);
            await JobHandler.SendAsync(taggedData);
            return await job.Value.Task;
        }

        private static ConcurrentDictionary<Guid, TaskCompletionSource<TOutput>> Jobs {
            get;
        } = new ConcurrentDictionary<Guid, TaskCompletionSource<TOutput>>();

        private static ExecutionDataflowBlockOptions Options {
            get;
        } = GetResultHandlerOptions();

        private static ITargetBlock<KeyValuePair<Guid, TOutput>> ResultHandler {
            get;
        } = CreateReplyHandler(Options);

        private IPropagatorBlock<KeyValuePair<Guid, TInput>, KeyValuePair<Guid, TOutput>> JobHandler {
            get;
        }

        private KeyValuePair<Guid, TInput> TagInputData(TInput data) {
            var id = Guid.NewGuid();
            return new KeyValuePair<Guid, TInput>(id, data);
        }

        private KeyValuePair<Guid, TaskCompletionSource<TOutput>> CreateJob(KeyValuePair<Guid, TInput> taggedData) {
            var id = taggedData.Key;
            var jobCompletionSource = new TaskCompletionSource<TOutput>();
            return new KeyValuePair<Guid, TaskCompletionSource<TOutput>>(id, jobCompletionSource);
        }

        private static ExecutionDataflowBlockOptions GetResultHandlerOptions() {
            return new ExecutionDataflowBlockOptions() {
                MaxDegreeOfParallelism = Environment.ProcessorCount,
                BoundedCapacity = 1000
            };
        }

        private static ITargetBlock<KeyValuePair<Guid, TOutput>> CreateReplyHandler(ExecutionDataflowBlockOptions options) {
            return new ActionBlock<KeyValuePair<Guid, TOutput>>((result) => {
                RecieveOutput(result);
            }, options);
        }

        private static void RecieveOutput(KeyValuePair<Guid, TOutput> result) {
            var jobId = result.Key;
            TaskCompletionSource<TOutput> jobCompletionSource;
            if (!Jobs.TryRemove(jobId, out jobCompletionSource)) {
                throw new InvalidOperationException($"The jobId: {jobId} was not found.");
            }
            var resultValue = result.Value;
            jobCompletionSource.SetResult(resultValue);            
        }
    }
}
命名空间ConcurrentFlows.DataflowJobs{
使用制度;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用System.Threading.Tasks;
使用System.Threading.Tasks.Dataflow;
/// 
///定义以下内容的通用接口:
///对于指定的输入类型=>将生成一个等待的结果。
/// 
///要处理的数据类型。
///消费者期望返回的数据类型。
公共接口管理器{
任务提交请求(TInput数据);
}
/// 
///基于TPL数据流的作业管理器。
/// 
///要处理的数据类型。
///消费者期望返回的数据类型。
公共类DataflowJobManager:IJobManager{
/// 
///预计jobHandler是一个注入的
///基于数据流的“计算器”的单例实例
///不依赖于它是一个单身汉。
/// 
///处理所有作业的单例数据流块。
公共数据流作业管理器(IPropagatorBlock作业处理程序){
如果(jobHandler==null){抛出新的ArgumentException(“参数不能为null。”,“jobHandler”);}
this.JobHandler=JobHandler;
如果(!alreadyLink){
LinkTo(ResultHandler,newdataflowLinkOptions(){PropagateCompletion=true});
alreadyLinked=true;
}
}
私有静态bool alreadyLinked=false;
/// 
///将请求提交给JobHandler并异步等待结果。
/// 
///要处理的输入数据。
public class Worker: IWorker
{
    private readonly IHttpClientManager _httpClient;
    private readonly ITokenService _tokenService;

    private readonly SemaphoreSlim _semaphore;

    public Worker(IHttpClientManager httpClient, ITokenService tokenService)
    {
        _httpClient = httpClient;
        _tokenService = tokenService;

        // we want to limit the number of items here
        _semaphore = new SemaphoreSlim(10);
    }

    public async Task<JObject> ProcessRequestAsync(string request, string route)
    {
        try
        {
            var accessToken = await _tokenService.GetTokenAsync(
                _timeSeriesConfiguration.TenantId,
                _timeSeriesConfiguration.ClientId,
                _timeSeriesConfiguration.ClientSecret);

            var cancellationToken = new CancellationTokenSource();

            cancellationToken.CancelAfter(30000);

            await _semaphore.WaitAsync(cancellationToken.Token);
            var httpResponseMessage = await _httpClient.SendAsync(new HttpClientRequest
            {
                Method = HttpMethod.Post,
                Uri = $"https://someuri/someroute",
                Token = accessToken,
                Content = request
            });

            var response = await httpResponseMessage.Content.ReadAsStringAsync();

            return response;
        }
        catch (Exception ex)
        {
            // do some logging

            throw;
        }
        finally
        {
            _semaphore.Release();
        }
    }
}
public class ThrottledExecution<T>
{
    private readonly ActionBlock<Task<Task<T>>> _actionBlock;
    private readonly CancellationToken _cancellationToken;

    public ThrottledExecution(int concurrencyLevel, int minDurationMilliseconds = 0,
        CancellationToken cancellationToken = default)
    {
        if (minDurationMilliseconds < 0) throw new ArgumentOutOfRangeException();
        _actionBlock = new ActionBlock<Task<Task<T>>>(async task =>
        {
            try
            {
                var delay = Task.Delay(minDurationMilliseconds, cancellationToken);
                task.RunSynchronously();
                await task.Unwrap().ConfigureAwait(false);
                await delay.ConfigureAwait(false);
            }
            catch { } // Ignore exceptions (errors are propagated through the task)
        }, new ExecutionDataflowBlockOptions()
        {
            MaxDegreeOfParallelism = concurrencyLevel,
            CancellationToken = cancellationToken,
        });
        _cancellationToken = cancellationToken;
    }

    public Task<T> Run(Func<Task<T>> function)
    {
        // Create a cold task (the function will be invoked later)
        var task = new Task<Task<T>>(function, _cancellationToken);
        var accepted = _actionBlock.Post(task);
        _cancellationToken.ThrowIfCancellationRequested();
        if (!accepted) throw new InvalidOperationException(
            "The component has been marked as complete.");
        return task.Unwrap();
    }

    public void Complete() => _actionBlock.Complete();
    public Task Completion => _actionBlock.Completion;
}
private ThrottledExecution<JObject> throttledExecution
    = new ThrottledExecution<JObject>(concurrencyLevel: 10);

public Task<JObject> GetContent(string request)
{
    return throttledExecution.Run(() => ProcessRequest(request));
}