异步接受传入请求

时间:2014-03-11 12:21:27

标签: c# multithreading asynchronous async-await asyncsocket

我已经在TCP应用程序中发现了一个瓶颈,我已经为此问题进行了简化。

我有一个MyClient类,表示客户端何时连接;我也有一个MyWrapper类,代表满足某些条件的客户端。如果MyClient满足某些条件,则它有资格使用包装器。

我想公开一个允许调用者等待MyWrapper的方法,该方法应该处理无效MyClients的协商和拒绝:

public static async Task StartAccepting(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        var wrapper = await AcceptWrapperAsync(token);

        HandleWrapperAsync(wrapper);
    }
}

因此AcceptWrapperAsync等待有效的包装器,HandleWrapperAsync异步处理包装器而不阻塞线程,因此AcceptWrapperAsync可以尽快恢复工作。

该方法如何在内部工作是这样的:

public static async Task<MyWrapper> AcceptWrapperAsync(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        var client = await AcceptClientAsync();
        if (IsClientWrappable(client))
            return new MyWrapper(client);
    }
    return null;
}

public static async Task<MyClient> AcceptClientAsync()
{
    await Task.Delay(1000);
    return new MyClient();
}

private static Boolean IsClientWrappable(MyClient client)
{
    Thread.Sleep(500);
    return true;
}

此代码模拟每秒都有一个客户端连接,并且如果连接适用于包装器,则需要半秒钟来检查。 AcceptWrapperAsync循环,直到生成一个有效的包装器,然后返回。

这种方法运作良好,有一个缺陷。在IsClientWrappable执行期间,没有其他客户端可以被接受,当许多客户端同时尝试连接时,会产生瓶颈。我担心在现实生活中,如果服务器在连接大量客户端时出现故障,那么上升并不会很好,因为所有这些都会尝试同时连接。我知道同时连接所有这些都很困难,但我想加快连接过程。

使IsClientWrappable异步,只是确保执行线程在协商完成之前不会被阻塞,但无论如何都会阻止执行流程。

我如何才能改进此方法以持续接受新客户,但仍然可以使用AcceptWrapperAsync等待包装?

2 个答案:

答案 0 :(得分:1)

//this loop must never be blocked
while (!token.IsCancellationRequested)
{
    var client = await AcceptClientAsync();
    HandleClientAsync(client); //must not block
}

Task HandleClientAsync(Client client) {
    if (await IsClientWrappableAsync(client)) //make async as well, don't block
        await HandleWrapperAsync(new MyWrapper(client));
}

这样您就可以将IsClientWrappable逻辑移出接受循环并进入后台异步工作流程。

如果您不希望IsClientWrappable无阻塞,请使用Task.Run将其换行。重要的是HandleClientAsync不会阻塞,以致其调用者也不会阻止。

答案 1 :(得分:1)

TPL Dataflow救援。我创建了一个&#34;生产者/消费者&#34;具有两个队列的对象:

  1. 接受来自&#34;制作人&#34;的输入。并将其存储在&#34; in&#34;队列中。
  2. 从&#34; in&#34;中读取的内部异步任务以给定的最大并行度并行排队和处理输入。
  3. 将处理过的物品放入&#34; out&#34;之后排队。结果或例外。
  4. 接受消费者await项目。然后可以检查处理是否成功。
  5. 我做了一些测试,似乎工作正常,我想做更多的测试:

    public sealed class ProcessingResult<TOut> 
        where TOut : class
    {
        public TOut Result { get; internal set; }
        public Exception Error { get; internal set; }
    }
    
    public abstract class ProcessingBufferBlock<TIn,TOut> 
        where TIn:class 
        where TOut:class
    {
        readonly BufferBlock<TIn> _in;
        readonly BufferBlock<ProcessingResult<TOut>> _out;
        readonly CancellationToken _cancellation;
        readonly SemaphoreSlim _semaphore;
    
        public ProcessingBufferBlock(Int32 boundedCapacity, Int32 degreeOfParalellism, CancellationToken cancellation)
        {
            _cancellation = cancellation;
            _semaphore = new SemaphoreSlim(degreeOfParalellism);
            var options = new DataflowBlockOptions() { BoundedCapacity = boundedCapacity, CancellationToken = cancellation };
            _in = new BufferBlock<TIn>(options);
            _out = new BufferBlock<ProcessingResult<TOut>>(options);
            StartReadingAsync();
        }
    
        private async Task StartReadingAsync()
        {
            await Task.Yield();
            while (!_cancellation.IsCancellationRequested)
            {
                var incoming = await _in.ReceiveAsync(_cancellation);
                ProcessThroughGateAsync(incoming);
            }
        }
    
        private async Task ProcessThroughGateAsync(TIn input)
        {
            _semaphore.Wait(_cancellation);
            Exception error=null;
            TOut result=null;
            try
            {
                result = await ProcessAsync(input);                   
            }
            catch (Exception ex)
            {
                error = ex;
            }
            finally
            {
                if(result!=null || error!=null)
                    _out.Post(new ProcessingResult<TOut>() { Error = error, Result = result });
                _semaphore.Release(1);
            }
        }
    
        protected abstract Task<TOut> ProcessAsync(TIn input);
    
        public void Post(TIn item)
        {
            _in.Post(item);
        }
    
        public Task<ProcessingResult<TOut>> ReceiveAsync()
        {
            return _out.ReceiveAsync();
        }
    }
    

    所以我在OP上使用的例子是这样的:

    public class WrapperProcessingQueue : ProcessingBufferBlock<MyClient, MyWrapper>
    {
        public WrapperProcessingQueue(Int32 boundedCapacity, Int32 degreeOfParalellism, CancellationToken cancellation)
            : base(boundedCapacity, degreeOfParalellism, cancellation)
        { }
    
        protected override async Task<MyWrapper> ProcessAsync(MyClient input)
        {
            await Task.Delay(5000);
            if (input.Id % 3 == 0)
                return null;
            return new MyWrapper(input);
        }
    }
    

    然后我可以尽快将MyClient个对象添加到该队列,它们将被并行处理,消费者将等待通过过滤器的那些。

    正如我所说,我想做更多测试,但任何反馈都会受到欢迎。

    干杯。