我有一个.NET Windows服务,它使用BeginRead / EndRead异步I / O范例实现套接字服务器。现在这个套接字代码需要调用一些async / Task / await异步代码。
我一直在使用Nito.AsyncEx库的AsyncContext类'Run方法,但我对从EndRead调用它是否会阻塞,保持工作线程人质有所保留。我得到my earlier question的建议是使用Task.Run而不是Nito.AsyncEx的AsyncContext.Run。这会将调用提交到async / await代码并立即返回。在我看来,在负载下,客户端没有阻止请求来充斥线程池。
我将重新询问我关于Nito的原始问题.AsyncEx的AsyncContext.Run:它是否保持调用的线程(池线程调用我的套接字的EndRead回调)作为人质,或者它是否在异步时释放该线程它调用的I / O是在后台发生的吗?
如果Nito.AsyncEx的AsyncContext.Run真正阻止,那么Task.Run似乎是我唯一的选择。关于如何回击客户端请求以防止线程池耗尽的任何建议?
答案 0 :(得分:7)
AsyncContext.Run
的目的是阻止所有异步操作完成。它确实保留了线程,直到发生这种情况。
我建议你重新考虑到目前为止的每一个假设:
End*
回调。如果您确定需要实现自己的TCP / IP服务器,并且无法在回调中同步工作,并且确实需要限制...那么请考虑Reactive Extensions或TPL Dataflow。这两个库都内置了可选的限制。
答案 1 :(得分:2)
这取决于您的应用程序究竟在做什么。以下是一些场景:
您正在处理来自每个客户的数据流
每个客户端都发送一些数据,您的服务器在处理每个消息时对其进行处理。在这种情况下,可能不需要在单独的线程中运行处理。在阻塞时,TCP堆栈正在构建一个缓冲区供您阅读。如果剩余的缓冲区大小太小,TCP堆栈将向客户端发送窗口大小警告。
客户可能会发送一连串消息,每个消息都需要经过漫长的处理
客户端以突发方式向您的服务器发送消息。每条消息都需要相当长的时间来处理。串行处理消息会导致大量TCP重试,并且会出现问题。在这种情况下,请关闭Task.Run
并且不要担心耗尽线程池。
客户端可能会发送的数据远远超出您处理的数据
处理来自客户端的消息非常耗时,客户可能会实际发送比您处理的更多的工作。在这种情况下,您可能需要某种特定于应用程序的流量控制。也许客户端需要在发送消息之前轮询服务器状态。如果客户端仍然发送,您将被迫忽略这些消息。