vto*_*ola 5 .net multithreading asynchronous tcplistener async-await
我有一个基于a的ECHO服务器应用程序TCPListener
.它接受客户端,读取数据并返回相同的数据.我使用async/await方法开发了它,使用XXXAsync
框架提供的方法.
我设置了性能计数器来测量输入和输出的消息和字节数,以及连接的插槽数.
我创建了一个启动1400异步的测试应用程序TCPClient
,并且每100-500ms发送一条1Kb消息.客户端在开始时有10-1000毫秒的随机等待启动,因此他们不会尝试同时连接所有客户端.我运作良好,我可以在PerfMonitor中看到1400连接,以良好的速率发送消息.我从另一台计算机上运行客户端应用程序 服务器的CPU和内存使用率非常低,它是带有8Gb RAM的Intel Core i7.客户端看起来比较忙,它是带有4Gb内存的i5,但仍然不是25%.
问题是如果我启动另一个客户端应用程序.连接在客户端中开始失败.我没有看到每秒消息的大幅增加(或多或少增加20%),但我看到连接客户端的数量大约是1900-2100,而不是预期的2800.性能略有下降,图表显示每秒最大和最小消息之间的差异比以前更大.
尽管如此,CPU使用率仍然不是40%,内存使用量仍然很少.我试图在客户端和服务器中增加数量或池线程:
ThreadPool.SetMaxThreads(5000, 5000);
ThreadPool.SetMinThreads(2000, 2000);
Run Code Online (Sandbox Code Playgroud)
在服务器中,循环接受连接:
while(true)
{
var client = await _server.AcceptTcpClientAsync();
HandleClientAsync(client);
}
Run Code Online (Sandbox Code Playgroud)
该HandleClientAsync
函数返回a Task
,但正如您所看到的循环不等待处理,只是继续接受另一个客户端.处理函数是这样的:
public async Task HandleClientAsync(TcpClient client)
{
while(ws.Connected && !_cancellation.IsCancellationRequested)
{
var msg = await ReadMessageAsync(client);
await WriteMessageAsync(client, msg);
}
}
Run Code Online (Sandbox Code Playgroud)
这两个函数只是异步读写流.
我看到我可以开始TCPListener
指示backlog
金额,但默认值是多少?
为什么可能是应用程序在达到最大CPU之前没有扩展的原因?
找出实际问题的方法和工具是什么?
UPDATE
我曾尝试Task.Yield
和Task.Run
办法,他们没有帮助.
服务器和客户端也在同一台计算机上本地运行.每秒增加客户端或消息量实际上会降低服务吞吐量.600个客户端每100ms发送一条消息,产生的吞吐量超过1000个客户端每100ms发送一条消息.
当连接超过~2000个客户端时,我在客户端看到的例外是两个.大约1500年我开始看到例外,但客户最终连接.超过1500我看到很多连接/断开:
"远程主机强行关闭现有连接"(System.Net.Sockets.SocketException)捕获到System.Net.Sockets.SocketException:"远程主机强行关闭现有连接"
"无法将数据写入传输连接:远程主机强制关闭现有连接." (System.IO.IOException)抛出System.IO.IOException:"无法将数据写入传输连接:远程主机强制关闭现有连接."
更新2
我已经使用async/await建立了一个非常简单的服务器和客户端项目,并按预期进行扩展.
我有可扩展性问题的项目就是这个WebSocket服务器,即使它使用相同的方法,显然也会引发争用.有一个托管组件的控制台应用程序,以及一个用于生成负载的控制台应用程序(尽管它至少需要Windows 8).
请注意,我不是要求直接解决问题的答案,而是要找出导致该争用的原因的技巧或方法.
我已经设法扩展到6,000个并发连接没有问题,每秒处理大约24,000条消息,从机器没有机器(没有localhost测试)连接,只使用大约80个物理线程.
我学到了一些教训:
除非你知道自己在做什么,否则不要这样做.
确保您释放调用线程以参与方法的其余部分.
从您的可执行应用程序中,如果您确信自己不在单线程同步上下文中,则允许任何线程获取延续而不是专门等待开始变为空闲的线程.
内存分析器显示应用程序在创建Byte[]
实例时花费了太多内存和时间.所以我设计了几种策略来重用可用的策略,或者只是"就地"工作而不是创建新的和复制.GC性能计数器(特别是"GC中的%时间",大约55%)引发了一些警告,即某些事情是不对的.此外,我正在使用BitArray
实例来检查以字节为单位的位,也导致一些内存开销,所以我用位操作替换它们并且它得到了改进.后来我发现WCF使用Byte[]
池来解决这个问题.
fast
异步允许很好地扩展,但它有成本.仅仅因为有可用的异步操作并不意味着你应该使用它.假设在获得实际响应之前需要等待一段时间,请使用异步编程.如果您确定数据存在或响应速度很快,请同步进行.
你必须实现两次方法,没有从同步代码重新同步异步的防弹方法.
归档时间: |
|
查看次数: |
1465 次 |
最近记录: |