我已经在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
等待包装?
答案 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;具有两个队列的对象:
await
项目。然后可以检查处理是否成功。我做了一些测试,似乎工作正常,我想做更多的测试:
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
个对象添加到该队列,它们将被并行处理,消费者将等待通过过滤器的那些。
正如我所说,我想做更多测试,但任何反馈都会受到欢迎。
干杯。